def _check_schedule(): current_time = datetime.datetime.now() check_start = current_time - datetime.timedelta(days=1) check_end = current_time + datetime.timedelta(days=1) rain = not options.manual_mode and (rain_blocks.block_end() > datetime.datetime.now() or inputs.rain_sensed()) active = log.active_runs() for entry in active: ignore_rain = stations.get(entry['station']).ignore_rain if entry['end'] <= current_time or (rain and not ignore_rain and not entry['blocked'] and not entry['manual']): log.finish_run(entry) if not entry['blocked']: stations.deactivate(entry['station']) if not options.manual_mode: schedule = predicted_schedule(check_start, check_end) #import pprint #logging.debug("Schedule: %s", pprint.pformat(schedule)) for entry in schedule: if entry['start'] <= current_time < entry['end']: log.start_run(entry) if not entry['blocked']: stations.activate(entry['station']) if stations.master is not None or options.master_relay: master_on = False # It's easy if we don't have to use delays: if options.master_on_delay == options.master_off_delay == 0: for entry in active: if not entry['blocked'] and stations.get(entry['station']).activate_master: master_on = True break else: # In manual mode we cannot predict, we only know what is currently running and the history if options.manual_mode: active = log.finished_runs() + active else: active = combined_schedule(check_start, check_end) for entry in active: if not entry['blocked'] and stations.get(entry['station']).activate_master: if entry['start'] + datetime.timedelta(seconds=options.master_on_delay) \ <= current_time < \ entry['end'] + datetime.timedelta(seconds=options.master_off_delay): master_on = True break if stations.master is not None: master_station = stations.get(stations.master) if master_on != master_station.active: master_station.active = master_on if options.master_relay: if master_on != outputs.relay_output: outputs.relay_output = master_on
def GET(self): qdict = web.input() if 'clear' in qdict: log.clear_runs() raise web.seeother('/log') if 'csv' in qdict: events = log.finished_runs() + log.active_runs() data = "Date, Start Time, Zone, Duration, Program\n" for interval in events: # return only records that are visible on this day: duration = (interval['end'] - interval['start']).total_seconds() minutes, seconds = divmod(duration, 60) data += ', '.join([ interval['start'].strftime("%Y-%m-%d"), interval['start'].strftime("%H:%M:%S"), str(interval['station']), "%02d:%02d" % (minutes, seconds), interval['program_name'] ]) + '\n' web.header('Content-Type', 'text/csv') web.header('Content-Disposition', 'attachment; filename="log.csv"') return data return self.core_render.log()
def set_stations_in_scheduler_off(): """Stoping selected station in scheduler.""" current_time = datetime.datetime.now() check_start = current_time - datetime.timedelta(days=1) check_end = current_time + datetime.timedelta(days=1) # In manual mode we cannot predict, we only know what is currently running and the history if options.manual_mode: active = log.finished_runs() + log.active_runs() else: active = combined_schedule(check_start, check_end) ending = False # active stations for entry in active: for used_stations in tank_options[ 'used_stations']: # selected stations for stoping if entry[ 'station'] == used_stations: # is this station in selected stations? log.finish_run(entry) # save end in log stations.deactivate(entry['station']) # stations to OFF ending = True if ending: log.info(NAME, _(u'Stoping stations in scheduler'))
def GET(self): statuslist = [] for station in stations.get(): if station.enabled or station.is_master or station.is_master_two: status = { 'station': station.index, 'status': 'on' if station.active else 'off', 'reason': 'master' if station.is_master or station.is_master_two else '', 'master': 1 if station.is_master else 0, 'master_two': 1 if station.is_master_two else 0, 'programName': '', 'remaining': 0 } if not station.is_master or not station.is_master_two: if options.manual_mode: status['programName'] = 'Manual Mode' else: if station.active: active = log.active_runs() for interval in active: if not interval['blocked'] and interval[ 'station'] == station.index: status['programName'] = interval[ 'program_name'] status['reason'] = 'program' status['remaining'] = max( 0, (interval['end'] - datetime.datetime.now() ).total_seconds()) elif not options.scheduler_enabled: status['reason'] = 'system_off' elif not station.ignore_rain and inputs.rain_sensed(): status['reason'] = 'rain_sensed' elif not station.ignore_rain and rain_blocks.seconds_left( ): status['reason'] = 'rain_delay' statuslist.append(status) web.header('Content-Type', 'application/json') return json.dumps(statuslist)
def combined_schedule(start_time, end_time): current_time = datetime.datetime.now() if current_time < start_time: result = predicted_schedule(start_time, end_time) elif current_time > end_time: result = [entry for entry in log.finished_runs() if start_time <= entry['start'] <= end_time or start_time <= entry['end'] <= end_time] else: result = log.finished_runs() result += log.active_runs() predicted = predicted_schedule(start_time, end_time) result += [entry for entry in predicted if current_time <= entry['start'] <= end_time] return result
def remaining_seconds(self): """Tries to figure out how long this output will be active. Returns 0 if no corresponding interval was found. Returns -1 if it should be considered infinite.""" from ospy.log import log active = log.active_runs() index = self.index result = 0 for interval in active: if not interval['blocked'] and interval['station'] == index: result = max(0, (interval['end'] - datetime.datetime.now()).total_seconds()) if result > datetime.timedelta(days=356).total_seconds(): result = -1 break return result
def run(self): # Activate outputs upon start if needed: current_time = datetime.datetime.now() rain = not options.manual_mode and ( rain_blocks.block_end() > datetime.datetime.now() or inputs.rain_sensed()) active = log.active_runs() for entry in active: ignore_rain = stations.get(entry['station']).ignore_rain if entry['end'] > current_time and ( not rain or ignore_rain) and not entry['blocked']: stations.activate(entry['station']) while True: self._check_schedule() time.sleep(1)
def run(self): # Activate outputs upon start if needed: current_time = datetime.datetime.now() rain = not options.manual_mode and (rain_blocks.block_end() > datetime.datetime.now() or inputs.rain_sensed()) active = log.active_runs() for entry in active: ignore_rain = stations.get(entry['station']).ignore_rain if entry['end'] > current_time and (not rain or ignore_rain) and not entry['blocked']: stations.activate(entry['station']) while True: try: self._check_schedule() except Exception: logging.warning('Scheduler error:\n' + traceback.format_exc()) time.sleep(1)
def run(self): while not self._stop_event.is_set(): try: log.clear(NAME) self._update_rev_data() if self.status['can_update'] and plugin_options[ 'auto_update'] and not log.active_runs(): perform_update() self.started.set() self._sleep(3600) except Exception: self.started.set() log.error(NAME, 'System update plug-in:\n' + traceback.format_exc()) self._sleep(60)
def combined_schedule(start_time, end_time): current_time = datetime.datetime.now() if current_time < start_time: result = predicted_schedule(start_time, end_time) elif current_time > end_time: result = [ entry for entry in log.finished_runs() if start_time <= entry['start'] <= end_time or start_time <= entry['end'] <= end_time ] else: result = log.finished_runs() result += log.active_runs() predicted = predicted_schedule(start_time, end_time) result += [ entry for entry in predicted if current_time <= entry['start'] <= end_time ] return result
def run(self): from ospy.options import options from ospy.log import log import logging while True: try: for repo in REPOS: self._repo_data[repo] = self._download_zip(repo) self._repo_contents[repo] = self.zip_contents(self._get_zip(repo)) status = options.plugin_status if options.auto_plugin_update and not log.active_runs(): for plugin in available(): update = self.available_version(plugin) if update is not None and plugin in status and status[plugin]['hash'] != update['hash']: logging.info('Updating the {} plug-in.'.format(plugin)) self.install_repo_plugin(update['repo'], plugin) except Exception: logging.error('Failed to update the plug-ins information:\n' + traceback.format_exc()) finally: self._sleep(3600)
def GET(self): statuslist = [] for station in stations.get(): if station.enabled or station.is_master or station.is_master_two: status = { 'station': station.index, 'status': 'on' if station.active else 'off', 'reason': 'master' if station.is_master or station.is_master_two else '', 'master': 1 if station.is_master else 0, 'master_two': 1 if station.is_master_two else 0, 'programName': '', 'remaining': 0} if not station.is_master or not station.is_master_two: if options.manual_mode: status['programName'] = 'Manual Mode' else: if station.active: active = log.active_runs() for interval in active: if not interval['blocked'] and interval['station'] == station.index: status['programName'] = interval['program_name'] status['reason'] = 'program' status['remaining'] = max(0, (interval['end'] - datetime.datetime.now()).total_seconds()) elif not options.scheduler_enabled: status['reason'] = 'system_off' elif not station.ignore_rain and inputs.rain_sensed(): status['reason'] = 'rain_sensed' elif not station.ignore_rain and rain_blocks.seconds_left(): status['reason'] = 'rain_delay' statuslist.append(status) web.header('Content-Type', 'application/json') return json.dumps(statuslist)
def calculate_balances(self): from scheduler import predicted_schedule now = datetime.datetime.now() for station in stations.get(): station.balance = {key: value for key, value in station.balance.iteritems() if key >= now.date() - datetime.timedelta(days=21)} if not station.balance or (now.date() - datetime.timedelta(days=21)) not in station.balance: station.balance[now.date() - datetime.timedelta(days=21)] = { 'eto': 0.0, 'rain': 0.0, 'intervals': [], 'total': 0.0, 'valid': True } runs = log.finished_runs() + log.active_runs() calc_day = now.date() - datetime.timedelta(days=20) while calc_day < now.date() + datetime.timedelta(days=7): if calc_day not in station.balance: station.balance[calc_day] = { 'eto': 4.0, 'rain': 0.0, 'intervals': [], 'total': 0.0, 'valid': False } try: if not station.balance[calc_day]['valid'] or calc_day >= now.date() - datetime.timedelta(days=1): station.balance[calc_day]['eto'] = station.eto_factor * weather.get_eto(calc_day) station.balance[calc_day]['rain'] = 0.0 if station.ignore_rain else weather.get_rain(calc_day) station.balance[calc_day]['valid'] = True except Exception: station.balance[calc_day]['valid'] = False logging.warning('Could not get weather information, using fallbacks:\n' + traceback.format_exc()) intervals = [] while runs and runs[0]['start'].date() <= calc_day: run = runs[0] if runs[0]['start'].date() == calc_day and not run['blocked'] and run['station'] == station.index: irrigation = (run['end'] - run['start']).total_seconds() / 3600 * station.precipitation if run['manual']: irrigation *= 0.5 # Only count half in case of manual runs intervals.append({ 'program': run['program'], 'program_name': run['program_name'], 'done': True, 'irrigation': irrigation }) del runs[0] if calc_day >= now.date(): if calc_day == now.date(): date_time_start = now else: date_time_start = datetime.datetime.combine(calc_day, datetime.time.min) date_time_end = datetime.datetime.combine(calc_day, datetime.time.max) for run in predicted_schedule(date_time_start, date_time_end): if not run['blocked'] and run['station'] == station.index: irrigation = (run['end'] - run['start']).total_seconds() / 3600 * station.precipitation intervals.append({ 'program': run['program'], 'program_name': run['program_name'], 'done': False, 'irrigation': irrigation }) if len(intervals) > len(station.balance[calc_day]['intervals']) or calc_day >= now.date(): station.balance[calc_day]['intervals'] = intervals station.balance[calc_day]['total'] = station.balance[calc_day - datetime.timedelta(days=1)]['total'] \ - station.balance[calc_day]['eto'] \ + station.balance[calc_day]['rain'] \ + sum(interval['irrigation'] for interval in station.balance[calc_day]['intervals']) station.balance[calc_day]['total'] = max(-100, min(station.balance[calc_day]['total'], station.capacity)) calc_day += datetime.timedelta(days=1) station.balance = station.balance # Force saving
def run(self): last_millis = 0 # timer for save log once_text = True # once_text to mini is auxiliary for blocking two_text = True three_text = True five_text = True six_text = True send = False mini = True sonic_cm = -1 level_in_tank = 0 regulation_text = _(u'Regulation NONE.') if NAME in rain_blocks: del rain_blocks[NAME] self._sleep(2) tank_mon = None if tank_options['use_footer']: tank_mon = showInFooter( ) # instantiate class to enable data in footer tank_mon.button = "tank_monitor/settings" # button redirect on footer tank_mon.label = _(u'Tank') # label on footer end = datetime.datetime.now() avg_lst = [0] * tank_options['avg_samples'] avg_cnt = 0 avg_rdy = False while not self._stop_event.is_set(): try: if tank_options['use_sonic']: if two_text: log.clear(NAME) log.info(NAME, _(u'Water tank monitor is enabled.')) once_text = True two_text = False ping_read = get_sonic_cm() if tank_options[ 'use_avg'] and ping_read > 0: # use averaging try: avg_lst[avg_cnt] = ping_read except: avg_lst.append(ping_read) avg_cnt += 1 if avg_cnt > tank_options['avg_samples']: avg_cnt = 0 avg_rdy = True if avg_rdy: sonic_cm = average_list(avg_lst) level_in_tank = get_sonic_tank_cm(sonic_cm) else: sonic_cm = 0 log.clear(NAME) log.info( NAME, _(u'Waiting for {} samples to be read from the sensor (when using averaging).' ).format(tank_options['avg_samples'])) else: # without averaging if ping_read > 0: # if sonic value is bad (-1) not use these sonic_cm = ping_read level_in_tank = get_sonic_tank_cm(sonic_cm) else: sonic_cm = 0 level_in_tank = 0 tempText = "" if level_in_tank > 0 and sonic_cm != 0: # if level is ok and sonic is ok three_text = True status['level'] = level_in_tank status['ping'] = sonic_cm status['volume'] = get_volume(level_in_tank) status['percent'] = get_tank(level_in_tank) log.clear(NAME) log.info( NAME, datetime_string() + ' ' + _(u'Water level') + ': ' + str(status['level']) + ' ' + _(u'cm') + ' (' + str(status['percent']) + ' ' + (u'%).')) if tank_options['check_liters']: # display in liters tempText = str( status['volume'] ) + ' ' + _(u'liters') + ', ' + str( status['level']) + ' ' + _(u'cm') + ' (' + str( status['percent']) + ' ' + (u'%)') log.info( NAME, _(u'Ping') + ': ' + str(status['ping']) + ' ' + _(u'cm') + ', ' + _(u'Volume') + ': ' + str(status['volume']) + ' ' + _(u'liters') + '.') else: tempText = str( status['volume'] ) + ' ' + _(u'm3') + ', ' + str( status['level']) + ' ' + _(u'cm') + ' (' + str( status['percent']) + ' ' + (u'%)') log.info( NAME, _(u'Ping') + ': ' + str(status['ping']) + ' ' + _(u'cm') + ', ' + _(u'Volume') + ': ' + str(status['volume']) + ' ' + _(u'm3') + '.') log.info( NAME, str(status['maxlevel_datetime']) + ' ' + _(u'Maximum Water level') + ': ' + str(status['maxlevel']) + ' ' + _(u'cm') + '.') log.info( NAME, str(status['minlevel_datetime']) + ' ' + _(u'Minimum Water level') + ': ' + str(status['minlevel']) + ' ' + _(u'cm') + '.') log.info(NAME, regulation_text) if tank_options[ 'enable_reg']: # if enable regulation "maximum water level" reg_station = stations.get( tank_options['reg_output']) if level_in_tank > tank_options[ 'reg_max']: # if actual level in tank > set maximum water level if five_text: five_text = False six_text = True regulation_text = datetime_string( ) + ' ' + _( u'Regulation set ON.' ) + ' ' + ' (' + _(u'Output') + ' ' + str( reg_station.index + 1) + ').' start = datetime.datetime.now() sid = reg_station.index end = datetime.datetime.now( ) + datetime.timedelta( seconds=tank_options['reg_ss'], minutes=tank_options['reg_mm']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _('Tank Monitor'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if level_in_tank < tank_options[ 'reg_min']: # if actual level in tank < set minimum water level if six_text: # blocking for once five_text = True six_text = False regulation_text = datetime_string( ) + ' ' + _( u'Regulation set OFF.' ) + ' ' + ' (' + _(u'Output') + ' ' + str( reg_station.index + 1) + ').' sid = reg_station.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) now = datetime.datetime.now() if now > end: # if program end in schedule release five_text to true in regulation for next scheduling # five_text = True six_text = False regulation_text = datetime_string() + ' ' + _( u'Waiting.') if status['level'] > status[ 'maxlevel']: # maximum level check status['maxlevel'] = status['level'] tank_options.__setitem__('saved_max', status['level']) status['maxlevel_datetime'] = datetime_string() log.info( NAME, datetime_string() + ': ' + _(u'Maximum has updated.')) update_log() if status['level'] < status['minlevel'] and status[ 'level'] > 2: # minimum level check status['minlevel'] = status['level'] tank_options.__setitem__('saved_min', status['level']) status['minlevel_datetime'] = datetime_string() log.info( NAME, datetime_string() + ': ' + _(u'Minimum has updated.')) update_log() if status['level'] <= int( tank_options['water_minimum'] ) and mini and not options.manual_mode and status[ 'level'] > 2: # level value is lower if tank_options[ 'use_send_email']: # if email is enabled send = True # send mini = False if tank_options['use_stop']: # if stop scheduler set_stations_in_scheduler_off() log.info( NAME, datetime_string() + ' ' + _(u'ERROR: Water in Tank') + ' < ' + str(tank_options['water_minimum']) + ' ' + _(u'cm') + _(u'!')) delaytime = int(tank_options['delay_duration']) if delaytime > 0: # if there is no water in the tank and the stations stop, then we set the rain delay for this time for blocking rain_blocks[NAME] = datetime.datetime.now( ) + datetime.timedelta( hours=float(delaytime)) if level_in_tank > int( tank_options['water_minimum'] ) + 5 and not mini: # refresh send email if actual level > options minimum +5 mini = True if NAME in rain_blocks and level_in_tank > int( tank_options['water_minimum']): del rain_blocks[NAME] if tank_options['enable_log']: millis = int(round(time.time() * 1000)) interval = (tank_options['log_interval'] * 60000) if (millis - last_millis) > interval: last_millis = millis update_log() elif level_in_tank == -1 and sonic_cm == 0: # waiting for samples tempText = _('Waiting for samples') else: # error probe tempText = _('FAULT') log.clear(NAME) log.info( NAME, datetime_string() + ' ' + _(u'Water level: Error.')) if tank_options[ 'use_water_err'] and three_text: # if probe has error send email three_text = False log.info( NAME, datetime_string() + ' ' + _(u'ERROR: Water probe has fault?')) msg = '<b>' + _( u'Water Tank Monitor plug-in' ) + '</b> ' + '<br><p style="color:red;">' + _( u'System detected error: Water probe has fault?' ) + '</p>' msglog = _( u'Water Tank Monitor plug-in' ) + ': ' + _( u'System detected error: Water probe has fault?' ) try: from plugins.email_notifications import try_mail try_mail( msg, msglog, attachment=None, subject=tank_options['emlsubject'] ) # try_mail(text, logtext, attachment=None, subject=None) except Exception: log.error( NAME, _(u'Water Tank Monitor plug-in') + ':\n' + traceback.format_exc()) if tank_options['use_water_stop']: if NAME not in rain_blocks: set_stations_in_scheduler_off() delaytime = int(tank_options['delay_duration']) if delaytime > 0: # if probe has fault, then we set the rain delay for this time for blocking rain_blocks[NAME] = datetime.datetime.now( ) + datetime.timedelta( hours=float(delaytime)) if tank_options['enable_reg']: tempText += ', ' + regulation_text if tank_options['use_footer']: if tank_mon is not None: tank_mon.val = tempText.encode('utf8').decode( 'utf8') # value on footer self._sleep(3) else: if once_text: log.clear(NAME) log.info(NAME, _(u'Water tank monitor is disabled.')) once_text = False two_text = True last_level = 0 self._sleep(1) if send: msg = '<b>' + _( u'Water Tank Monitor plug-in' ) + '</b> ' + '<br><p style="color:red;">' + _( u'System detected error: Water Tank has minimum Water Level' ) + ': ' + str(tank_options['water_minimum']) + _( u'cm') + '.\n' + '</p>' msglog = _(u'Water Tank Monitor plug-in') + ': ' + _( u'System detected error: Water Tank has minimum Water Level' ) + ': ' + str( tank_options['water_minimum']) + _(u'cm') + '. ' send = False try: try_mail = None if tank_options['eplug'] == 0: # email_notifications from plugins.email_notifications import try_mail if tank_options[ 'eplug'] == 1: # email_notifications SSL from plugins.email_notifications_ssl import try_mail if try_mail is not None: try_mail( msg, msglog, attachment=None, subject=tank_options['emlsubject'] ) # try_mail(text, logtext, attachment=None, subject=None) except Exception: log.info( NAME, _(u'E-mail not send! The Email Notifications plug-in is not found in OSPy or not correctly setuped.' )) log.error( NAME, _(u'Water Tank Monitor plug-in') + ':\n' + traceback.format_exc()) except Exception: log.clear(NAME) log.error( NAME, _(u'Water Tank Monitor plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
def GET(self): from ospy import server qdict = web.input() stop_all = get_input(qdict, 'stop_all', False, lambda x: True) scheduler_enabled = get_input(qdict, 'scheduler_enabled', None, lambda x: x == '1') manual_mode = get_input(qdict, 'manual_mode', None, lambda x: x == '1') rain_block = get_input(qdict, 'rain_block', None, float) level_adjustment = get_input(qdict, 'level_adjustment', None, float) toggle_temp = get_input(qdict, 'toggle_temp', False, lambda x: True) if stop_all: if not options.manual_mode: options.scheduler_enabled = False programs.run_now_program = None run_once.clear() log.finish_run(None) stations.clear() if scheduler_enabled is not None: options.scheduler_enabled = scheduler_enabled if manual_mode is not None: options.manual_mode = manual_mode if rain_block is not None: options.rain_block = datetime.datetime.now() + datetime.timedelta(hours=rain_block) if level_adjustment is not None: options.level_adjustment = level_adjustment / 100 if toggle_temp: options.temp_unit = "F" if options.temp_unit == "C" else "C" set_to = get_input(qdict, 'set_to', None, int) sid = get_input(qdict, 'sid', 0, int) - 1 set_time = get_input(qdict, 'set_time', 0, int) if set_to is not None and 0 <= sid < stations.count() and options.manual_mode: if set_to: # if status is on start = datetime.datetime.now() new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': "Manual", 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': start + datetime.timedelta(days=3650), 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } if set_time > 0: # if an optional duration time is given new_schedule['end'] = datetime.datetime.now() + datetime.timedelta(seconds=set_time) log.start_run(new_schedule) stations.activate(new_schedule['station']) else: # If status is off stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) self._redirect_back()
def run(self): Temperature = 0 Humidity = 0 last_millis = 0 # timer for save log var1 = True # Auxiliary variable for once on var2 = True # Auxiliary variable for once off air_temp = None if plugin_options['use_footer']: air_temp = showInFooter( ) # instantiate class to enable data in footer air_temp.button = "air_temp_humi/settings" # button redirect on footer air_temp.label = _(u'Temperature') # label on footer regulation_text = '' #flow = showOnTimeline() # instantiate class to enable data display #flow.unit = _(u'Liters') #flow.val = 10 #flow.clear while not self._stop_event.is_set(): try: if plugin_options['enabled']: # if plugin is enabled log.clear(NAME) log.info(NAME, datetime_string()) tempText = "" if plugin_options[ 'enable_dht']: # if DHTxx probe is enabled try: result = 1 # 1=ERR_MISSING_DATA if plugin_options['dht_type'] == 0: # DHT11 result = instance.read() if plugin_options['dht_type'] == 1: # DHT22 result = instance22.read() if result.is_valid( ): # 0=ERR_NO_ERROR, 1=ERR_MISSING_DATA, 2=ERR_CRC Temperature = result.temperature Humidity = result.humidity global tempDHT, humiDHT tempDHT = Temperature humiDHT = Humidity except: log.clear(NAME) log.info(NAME, datetime_string()) if plugin_options['dht_type'] == 0: # DHT11 log.info(NAME, _(u'DHT11 data is not valid')) tempText += ' ' + _(u'DHT11 data is not valid') if plugin_options['dht_type'] == 1: # DHT22 log.info(NAME, _(u'DHT22 data is not valid')) tempText += ' ' + _(u'DHT22 data is not valid') if Humidity and Temperature != 0: self.status['temp'] = Temperature self.status['humi'] = Humidity log.info( NAME, _(u'Temperature') + ' ' + _(u'DHT') + ': ' + u'%.1f \u2103' % Temperature) log.info( NAME, _(u'Humidity') + ' ' + _(u'DHT') + ': ' + u'%.1f' % Humidity + ' %RH') tempText += ' ' + _( u'DHT' ) + ': ' + u'%.1f \u2103' % Temperature + ' ' + u'%.1f' % Humidity + ' %RH' + ' ' if plugin_options['enabled_reg']: log.info(NAME, regulation_text) station = stations.get( plugin_options['control_output']) if plugin_options[ 'enabled_reg']: # if regulation is enabled if Humidity > (plugin_options['humidity_on'] + plugin_options['hysteresis'] / 2) and var1 is True: start = datetime.datetime.now() sid = station.index end = datetime.datetime.now( ) + datetime.timedelta( seconds=plugin_options['reg_ss'], minutes=plugin_options['reg_mm']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _('Air Temperature'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) var1 = False var2 = True self.status['outp'] = 1 regulation_text = datetime_string( ) + ' ' + _( u'Regulation set ON.' ) + ' ' + ' (' + _('Output') + ' ' + str( station.index + 1) + ').' update_log(self.status) if Humidity < (plugin_options['humidity_off'] - plugin_options['hysteresis'] / 2) and var2 is True: sid = station.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) var1 = True var2 = False self.status['outp'] = 0 regulation_text = datetime_string( ) + ' ' + _( u'Regulation set OFF.' ) + ' ' + ' (' + _('Output') + ' ' + str( station.index + 1) + ').' update_log(self.status) if plugin_options[ 'ds_enabled']: # if in plugin is enabled DS18B20 DS18B20_read_data( ) # get read DS18B20 temperature data to global tempDS[xx] tempText += _(u'DS') + ': ' for i in range(0, plugin_options['ds_used']): self.status['DS%d' % i] = tempDS[i] log.info( NAME, _(u'Temperature') + ' ' + _(u'DS') + str(i + 1) + ' (' + u'%s' % plugin_options['label_ds%d' % i] + '): ' + u'%.1f \u2103' % self.status['DS%d' % i]) tempText += u' %s' % plugin_options[ 'label_ds%d' % i] + u' %.1f \u2103' % self.status['DS%d' % i] if plugin_options['enabled_reg']: tempText += ' ' + regulation_text if plugin_options['use_footer']: if air_temp is not None: if is_python2(): air_temp.val = tempText # value on footer else: air_temp.val = tempText.encode('utf8').decode( 'utf8') # value on footer if plugin_options['enable_log']: # enabled logging millis = int(round(time.time() * 1000)) interval = (plugin_options['log_interval'] * 60000) if (millis - last_millis) > interval: last_millis = millis update_log(self.status) self._sleep(5) except Exception: log.error( NAME, _(u'Air Temperature and Humidity Monitor plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
def run(self): old_statuslist = ' ' log.clear(NAME) log.info(NAME, _('MQTT Zones Plugin started.')) once = True while not self._stop.is_set(): if plugin_options['use_mqtt']: once = True try: statuslist = [] for station in stations.get(): if station.enabled or station.is_master or station.is_master_two: status = { 'station': station.index, 'status': 'on' if station.active else 'off', 'name': station.name, 'reason': 'master' if station.is_master or station.is_master_two else ''} if not station.is_master or not station.is_master_two: if station.active: active = log.active_runs() for interval in active: if not interval['blocked'] and interval['station'] == station.index: status['reason'] = 'program' elif not options.scheduler_enabled: status['reason'] = 'system_off' elif not station.ignore_rain and inputs.rain_sensed(): status['reason'] = 'rain_sensed' elif not station.ignore_rain and rain_blocks.seconds_left(): status['reason'] = 'rain_delay' statuslist.append(status) zone_topic = plugin_options['zone_topic'] if zone_topic: try: from plugins import mqtt except ImportError: log.error(NAME, _('MQTT Zones Plugin requires MQTT plugin.')) if statuslist != old_statuslist: old_statuslist = statuslist client = mqtt.get_client() if client: client.publish(zone_topic, json.dumps(statuslist), qos=1, retain=True) log.clear(NAME) log.info(NAME, _('MQTT Zones Plugin public') + ':\n' + str(statuslist)) else: log.clear(NAME) log.error(NAME, _('Not setup Zone Topic')) self._sleep(10) self._sleep(1) except Exception: log.error(NAME, _('MQTT Zones plug-in') + ':\n' + traceback.format_exc()) self._sleep(60) else: if once: log.info(NAME, _('MQTT Zones Plugin is disabled.')) once = False
def _check_schedule(): current_time = datetime.datetime.now() check_start = current_time - datetime.timedelta(days=1) check_end = current_time + datetime.timedelta(days=1) rain = not options.manual_mode and ( rain_blocks.block_end() > datetime.datetime.now() or inputs.rain_sensed()) active = log.active_runs() for entry in active: ignore_rain = stations.get(entry['station']).ignore_rain if entry['end'] <= current_time or (rain and not ignore_rain and not entry['blocked'] and not entry['manual']): log.finish_run(entry) if not entry['blocked']: stations.deactivate(entry['station']) if not options.manual_mode: schedule = predicted_schedule(check_start, check_end) #import pprint #logging.debug("Schedule: %s", pprint.pformat(schedule)) for entry in schedule: if entry['start'] <= current_time < entry['end']: log.start_run(entry) if not entry['blocked']: stations.activate(entry['station']) if stations.master is not None: master_on = False # It's easy if we don't have to use delays: if options.master_on_delay == options.master_off_delay == 0: for entry in active: if not entry['blocked'] and stations.get( entry['station']).activate_master: master_on = True break else: # In manual mode we cannot predict, we only know what is currently running and the history if options.manual_mode: active = log.finished_runs() + active else: active = combined_schedule(check_start, check_end) for entry in active: if not entry['blocked'] and stations.get( entry['station']).activate_master: if entry['start'] + datetime.timedelta(seconds=options.master_on_delay) \ <= current_time < \ entry['end'] + datetime.timedelta(seconds=options.master_off_delay): master_on = True break if stations.master is not None: master_station = stations.get(stations.master) if master_on != master_station.active: master_station.active = master_on if options.master_relay: outputs.relay_output = master_on if stations.master_two is not None: master_two_on = False # It's easy if we don't have to use delays: if options.master_on_delay_two == options.master_off_delay_two == 0: for entry in active: if not entry['blocked'] and stations.get( entry['station']).activate_master_two: master_two_on = True break else: # In manual mode we cannot predict, we only know what is currently running and the history if options.manual_mode: active = log.finished_runs() + active else: active = combined_schedule(check_start, check_end) for entry in active: if not entry['blocked'] and stations.get( entry['station']).activate_master_two: if entry['start'] + datetime.timedelta(seconds=options.master_on_delay) \ <= current_time < \ entry['end'] + datetime.timedelta(seconds=options.master_off_delay): master_two_on = True break if stations.master_two is not None: master_station_two = stations.get(stations.master_two) if master_two_on != master_station_two.active: master_station_two.active = master_two_on
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 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 on_message(client, userdata, message): log.clear(NAME) log.info(NAME, datetime_string() + ' ' + _('Message received') + ': ' + str(message.payload.decode("utf-8"))) #print("Message topic=",message.topic) #print("Message qos=",message.qos) #print("Message retain flag=",message.retain) try: cmd = json.loads(message.payload) except: cmd = [] status = "RunOnce Error" client.publish(plugin_options['publish_up_down'], status) pass try: log.info(NAME, datetime_string() + ' ' + _(u'Try-ing to processing command.')) num_sta = options.output_count if type(cmd) is list: # cmd is list if len(cmd) < num_sta: log.info(NAME, datetime_string() + ' ' + _(u'Not enough stations specified, assuming first {} of {}').format(len(cmd), num_sta)) rovals = cmd + ([0] * (num_sta - len(cmd))) elif len(cmd) > num_sta: log.info(NAME, datetime_string() + ' ' + _(u'Too many stations specified, truncating to {}').format(num_sta)) rovals = cmd[0:num_sta] else: rovals = cmd elif type(cmd) is dict: # cmd is dictionary rovals = [0] * num_sta snames = station_names() # Load station names from file jnames = json.loads(snames) # Load as json for k, v in list(cmd.items()): if k not in snames: # station name in dict is not in OSPy stations name (ERROR) log.warning(NAME, _(u'No station named') + (u': %s') % k) else: # station name in dict is in OSPy stations name (OK) # v is value for time, k is station name in dict rovals[jnames.index(k)] = v else: log.error(NAME, datetime_string() + ' ' + _(u'Unexpected command') + (u': %s') % message.payload) rovals = None status = "RunOnce Error" client.publish(plugin_options['publish_up_down'], status) if rovals is not None and any(rovals): for i in range(0, len(rovals)): sid = i start = datetime.datetime.now() try: end = datetime.datetime.now() + datetime.timedelta(seconds=int(rovals[i])) except: end = datetime.datetime.now() log.error(NAME, _(u'MQTT Run-once plug-in') + ':\n' + traceback.format_exc()) pass new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _(u'MQTT Run-once'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if int(rovals[i]) < 1: # station has no time for run (stoping) stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) status = "RunOnce Processing" client.publish(plugin_options['publish_up_down'], status) except Exception: log.clear(NAME) log.error(NAME, _(u'MQTT Run-once plug-in') + ':\n' + traceback.format_exc()) status = "RunOnce Error" client.publish(plugin_options['publish_up_down'], status) pass
def on_message(client, userdata, message): log.clear(NAME) log.info( NAME, datetime_string() + ' ' + _('Message received') + ': ' + str(message.payload.decode("utf-8"))) #print("Message topic=",message.topic) #print("Message qos=",message.qos) #print("Message retain flag=",message.retain) if plugin_options["use_mqtt_secondary"]: if not options.manual_mode: # check operation status log.info( NAME, datetime_string() + ' ' + _('You must this OSPy switch to manual mode!')) return if inputs.rain_sensed(): # check rain sensor log.info( NAME, datetime_string() + ' ' + _('Skipping command, rain sense is detected!')) return if rain_blocks.seconds_left(): # check rain delay log.info( NAME, datetime_string() + ' ' + _('Skipping command, rain delay is activated!')) return try: rec_data = str(message.payload) cmd = json.loads(rec_data) except ValueError as e: log.info(NAME, _('Could not decode command:') + ':\n' + message.payload, e) return first = int( plugin_options["first_station"]) - 1 # first secondary station count = int(plugin_options["station_count"]) # count secondary station try: for i in range( first, first + count): # count of station (example on main OSPy: 5 to 10) zone = cmd[i]["status"] # "off" or "on" state #station = cmd[i]["station"] #name = cmd[i]["name"] #print station, name, zone sid = i - first if sid <= stations.count(): # local station size check if zone == "on": start = datetime.datetime.now() mqn = _('MQTT Manual') new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': mqn, 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': start + datetime.timedelta(days=3650), 'uid': '%s-%s-%d' % (str(start), mqn, sid), 'usage': stations.get(sid).usage } # if an optional duration time is given #set_time = xxx second #new_schedule['end'] = datetime.datetime.now() + datetime.timedelta(seconds=set_time) log.start_run(new_schedule) stations.activate(new_schedule['station']) if zone == "off": stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == i: log.finish_run(interval) else: log.error( NAME, _('MQTT plug-in') + ':\n' + _('Setup stations count is smaler! Set correct first station and station count.' )) except Exception: log.error(NAME, _('MQTT plug-in') + ':\n' + traceback.format_exc()) time.sleep(1)
def run(self): temperature_ds = [-127, -127, -127, -127, -127, -127] msg_a_on = True msg_a_off = True last_text = '' send = False temp_sw = None if plugin_options['use_footer']: temp_sw = showInFooter( ) # instantiate class to enable data in footer temp_sw.button = "pool_heating/settings" # button redirect on footer temp_sw.label = _('Pool Heating') # label on footer ds_a_on = -127.0 ds_a_off = -127.0 millis = 0 # timer for clearing status on the web pages after 5 sec last_millis = int(round(time.time() * 1000)) safety_start = datetime.datetime.now() safety_end = datetime.datetime.now() + datetime.timedelta( minutes=plugin_options['safety_mm']) a_state = -3 # for state in footer "Waiting." regulation_text = _(u'Waiting to turned on or off.') if not plugin_options['enabled_a']: a_state = -1 # for state in footer "Waiting (not enabled regulation in options)." log.info(NAME, datetime_string() + ' ' + _('Waiting.')) end = datetime.datetime.now() while not self._stop_event.is_set(): try: if plugin_options[ "sensor_probe"] == 2: # loading probe name from plugin air_temp_humi try: from plugins.air_temp_humi import plugin_options as air_temp_data self.status['ds_name_0'] = air_temp_data['label_ds0'] self.status['ds_name_1'] = air_temp_data['label_ds1'] self.status['ds_name_2'] = air_temp_data['label_ds2'] self.status['ds_name_3'] = air_temp_data['label_ds3'] self.status['ds_name_4'] = air_temp_data['label_ds4'] self.status['ds_name_5'] = air_temp_data['label_ds5'] self.status['ds_count'] = air_temp_data['ds_used'] from plugins.air_temp_humi import DS18B20_read_probe # value with temperature from probe DS1-DS6 temperature_ds = [ DS18B20_read_probe(0), DS18B20_read_probe(1), DS18B20_read_probe(2), DS18B20_read_probe(3), DS18B20_read_probe(4), DS18B20_read_probe(5) ] except: log.error( NAME, _('Unable to load settings from Air Temperature and Humidity Monitor plugin! Is the plugin Air Temperature and Humidity Monitor installed and set up?' )) pass # regulation if plugin_options['enabled_a'] and plugin_options[ "sensor_probe"] != 0: # enabled regulation and selected input for probes sensor/airtemp plugin if plugin_options["sensor_probe"] == 1 and sensors.count( ) > 0: sensor_on = sensors.get( int(plugin_options['probe_A_on_sens'])) # pool if sensor_on.sens_type == 5: # temperature sensor ds_a_on = self._try_read(sensor_on.last_read_value) elif sensor_on.sens_type == 6 and sensor_on.multi_type == 0: # multitemperature sensor DS1 ds_a_on = self._try_read( sensor_on.last_read_value[0]) elif sensor_on.sens_type == 6 and sensor_on.multi_type == 1: # multitemperature sensor DS2 ds_a_on = self._try_read( sensor_on.last_read_value[1]) elif sensor_on.sens_type == 6 and sensor_on.multi_type == 2: # multitemperature sensor DS3 ds_a_on = self._try_read( sensor_on.last_read_value[2]) elif sensor_on.sens_type == 6 and sensor_on.multi_type == 3: # multitemperature sensor DS4 ds_a_on = self._try_read( sensor_on.last_read_value[3]) else: ds_a_on = -127.0 sensor_off = sensors.get( int(plugin_options['probe_A_off_sens'])) # solar if sensor_off.sens_type == 5: # temperature sensor ds_a_off = self._try_read( sensor_off.last_read_value) elif sensor_off.sens_type == 6 and sensor_off.multi_type == 0: # multitemperature sensor DS1 ds_a_off = self._try_read( sensor_off.last_read_value[0]) elif sensor_off.sens_type == 6 and sensor_off.multi_type == 1: # multitemperature sensor DS2 ds_a_off = self._try_read( sensor_off.last_read_value[1]) elif sensor_off.sens_type == 6 and sensor_off.multi_type == 2: # multitemperature sensor DS3 ds_a_off = self._try_read( sensor_off.last_read_value[2]) elif sensor_off.sens_type == 6 and sensor_off.multi_type == 3: # multitemperature sensor DS4 ds_a_off = self._try_read( sensor_off.last_read_value[3]) else: ds_a_off = -127.0 elif plugin_options["sensor_probe"] == 2: ds_a_on = temperature_ds[ plugin_options['probe_A_on']] # pool ds_a_off = temperature_ds[ plugin_options['probe_A_off']] # solar station_a = stations.get( plugin_options['control_output_A']) # only for testing... without airtemp plugin or sensors #ds_a_on = 15 #ds_a_off = 25 probes_ok = True if ds_a_on == -127.0 or ds_a_off == -127.0: probes_ok = False a_state = -2 # The station switches off if the sensors has a fault sid = station_a.index active = log.active_runs() for interval in active: if interval['station'] == sid: stations.deactivate(sid) log.finish_run(interval) regulation_text = datetime_string() + ' ' + _( 'Regulation set OFF.') + ' ' + ' (' + _( 'Output') + ' ' + str(station_a.index + 1) + ').' log.clear(NAME) log.info(NAME, regulation_text) # release msg_a_on and msg_a_off to true for future regulation (after probes is ok) msg_a_on = True msg_a_off = True if (ds_a_off - ds_a_on ) > plugin_options['temp_a_on'] and probes_ok: # ON a_state = 1 if msg_a_on: msg_a_on = False msg_a_off = True regulation_text = datetime_string() + ' ' + _( 'Regulation set ON.') + ' ' + ' (' + _( 'Output') + ' ' + str(station_a.index + 1) + ').' log.clear(NAME) log.info(NAME, regulation_text) start = datetime.datetime.now() sid = station_a.index end = datetime.datetime.now() + datetime.timedelta( seconds=plugin_options['reg_ss'], minutes=plugin_options['reg_mm']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _(u'Pool Heating'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if plugin_options[ 'enabled_safety']: # safety check safety_end = datetime.datetime.now( ) + datetime.timedelta( minutes=plugin_options['safety_mm']) if (ds_a_off - ds_a_on ) < plugin_options['temp_a_off'] and probes_ok: # OFF a_state = 0 if msg_a_off: msg_a_off = False msg_a_on = True regulation_text = datetime_string() + ' ' + _( 'Regulation set OFF.') + ' ' + ' (' + _( 'Output') + ' ' + str(station_a.index + 1) + ').' log.clear(NAME) log.info(NAME, regulation_text) sid = station_a.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) if plugin_options[ 'enabled_safety']: # safety check safety_end = datetime.datetime.now( ) + datetime.timedelta( minutes=plugin_options['safety_mm']) ### if "pool" end in schedule release msg_a_on to true in regulation for next scheduling ### now = datetime.datetime.now() if now > end: msg_a_off = False msg_a_on = True if probes_ok: a_state = -3 if plugin_options['enabled_safety']: # safety check safety_start = datetime.datetime.now() if safety_start > safety_end and probes_ok: # is time for check if (ds_a_off - ds_a_on) > plugin_options[ 'temp_safety']: # temperature difference is bigger a_state = -4 msg_a_off = False msg_a_on = True regulation_text = datetime_string() + ' ' + _( 'Safety shutdown!' ) + ' ' + ' (' + _('Output') + ' ' + str( station_a.index + 1) + ').\n' + _('Regulation disabled!') log.clear(NAME) log.info(NAME, regulation_text) sid = station_a.index stations.deactivate(sid) active = log.active_runs() for interval in active: # stop stations if interval['station'] == sid: log.finish_run(interval) send = True # send e-mail plugin_options[ 'enabled_a'] = False # disabling plugin else: if a_state != -4: # if safety error not print waiting also safety shutdown! a_state = -1 # footer text tempText = ' ' if a_state == 0: tempText += regulation_text if a_state == 1: tempText += regulation_text if a_state == -1: tempText = _( 'Waiting (not enabled regulation in options, or not selected input).' ) if a_state == -2: tempText = _( 'Some probe shows a fault, regulation is blocked!') if a_state == -3: tempText = _('Waiting.') if a_state == -4: tempText = _('Safety shutdown!') if plugin_options['use_footer']: if temp_sw is not None: temp_sw.val = tempText.encode('utf8').decode( 'utf8') # value on footer self._sleep(2) millis = int(round(time.time() * 1000)) if (millis - last_millis ) > 5000: # 5 second to clearing status on the webpage last_millis = millis log.clear(NAME) if plugin_options["sensor_probe"] == 1: try: if options.temp_unit == 'C': log.info( NAME, datetime_string() + '\n' + sensor_on.name + ' (' + _('Pool') + ') %.1f \u2103 \n' % ds_a_on + sensor_off.name + ' (' + _('Solar') + ') %.1f \u2103' % ds_a_off) else: log.info( NAME, datetime_string() + '\n' + sensor_on.name + ' (' + _('Pool') + ') %.1f \u2109 \n' % ds_a_on + sensor_off.name + ' (' + _('Solar') + ') %.1f \u2103' % ds_a_off) except: pass elif plugin_options["sensor_probe"] == 2: log.info( NAME, datetime_string() + '\n' + _('Pool') + u' %.1f \u2103 \n' % ds_a_on + _('Solar') + ' %.1f \u2103' % ds_a_off) if last_text != tempText: log.info(NAME, tempText) last_text = tempText if send: msg = '<b>' + _( 'Pool Heating plug-in' ) + '</b> ' + '<br><p style="color:red;">' + _( 'System detected error: The temperature did not drop when the pump was switched on after the setuped time. Stations set to OFF. Safety shutdown!' ) + '</p>' msglog = _( 'System detected error: The temperature did not drop when the pump was switched on after the setuped time. Stations set to OFF. Safety shutdown!' ) send = False try: try_mail = None if plugin_options['eplug'] == 0: # email_notifications from plugins.email_notifications import try_mail if plugin_options[ 'eplug'] == 1: # email_notifications SSL from plugins.email_notifications_ssl import try_mail if try_mail is not None: try_mail( msg, msglog, attachment=None, subject=plugin_options['emlsubject'] ) # try_mail(text, logtext, attachment=None, subject=None) except Exception: log.error( NAME, _('Pool Heating plug-in') + ':\n' + traceback.format_exc()) self._sleep(2) except Exception: log.error( NAME, _('Pool Heating plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
def calculate_balances(self): from scheduler import predicted_schedule now = datetime.datetime.now() for station in stations.get(): station.balance = { key: value for key, value in station.balance.iteritems() if key >= now.date() - datetime.timedelta(days=21) } if not station.balance or (now.date() - datetime.timedelta(days=21) ) not in station.balance: station.balance[now.date() - datetime.timedelta(days=21)] = { 'eto': 0.0, 'rain': 0.0, 'intervals': [], 'total': 0.0, 'valid': True } runs = log.finished_runs() + log.active_runs() calc_day = now.date() - datetime.timedelta(days=20) while calc_day < now.date() + datetime.timedelta(days=10): if calc_day not in station.balance: station.balance[calc_day] = { 'eto': 4.0, 'rain': 0.0, 'intervals': [], 'total': 0.0, 'valid': False } try: if not station.balance[calc_day][ 'valid'] or calc_day >= now.date(): station.balance[calc_day]['eto'] = weather.get_eto( calc_day) station.balance[calc_day]['rain'] = weather.get_rain( calc_day) station.balance[calc_day]['valid'] = True except Exception: station.balance[calc_day]['valid'] = False logging.warning( 'Could not get weather information, using fallbacks:\n' + traceback.format_exc()) intervals = [] while runs and runs[0]['start'].date() <= calc_day: run = runs[0] if runs[0]['start'].date() == calc_day and not run[ 'blocked'] and run['station'] == station.index: irrigation = (run['end'] - run['start']).total_seconds( ) / 3600 * station.precipitation if run['manual']: irrigation *= 0.5 # Only count half in case of manual runs intervals.append({ 'program': run['program'], 'program_name': run['program_name'], 'done': True, 'irrigation': irrigation }) del runs[0] if calc_day >= now.date(): if calc_day == now.date(): date_time_start = now else: date_time_start = datetime.datetime.combine( calc_day, datetime.time.min) date_time_end = datetime.datetime.combine( calc_day, datetime.time.max) for run in predicted_schedule(date_time_start, date_time_end): if not run['blocked'] and run[ 'station'] == station.index: irrigation = ( run['end'] - run['start'] ).total_seconds() / 3600 * station.precipitation intervals.append({ 'program': run['program'], 'program_name': run['program_name'], 'done': False, 'irrigation': irrigation }) if len(intervals) > len(station.balance[calc_day]['intervals'] ) or calc_day >= now.date(): station.balance[calc_day]['intervals'] = intervals station.balance[calc_day]['total'] = station.balance[calc_day - datetime.timedelta(days=1)]['total'] \ - station.balance[calc_day]['eto'] \ + station.balance[calc_day]['rain'] \ + sum(interval['irrigation'] for interval in station.balance[calc_day]['intervals']) station.balance[calc_day]['total'] = max( -100, min(station.balance[calc_day]['total'], station.capacity)) calc_day += datetime.timedelta(days=1) station.balance = station.balance # Force saving
def run(self): temperature_ds = [-127, -127, -127, -127, -127, -127] msg_a_on = True msg_a_off = True msg_b_on = True msg_b_off = True msg_c_on = True msg_c_off = True temp_sw = None if plugin_options['use_footer']: temp_sw = showInFooter( ) # instantiate class to enable data in footer temp_sw.button = "temperature_switch/settings" # button redirect on footer temp_sw.label = _(u'Temperature Switch') # label on footer millis = int(round( time.time() * 1000)) # timer for clearing status on the web pages after 60 sec last_millis = millis a_state = -1 # for state in footer (-1 disable regulation A, 0 = Aoff, 1 = Aon) b_state = -1 c_state = -1 helper_text = '' while not self._stop_event.is_set(): try: try: from plugins.air_temp_humi import plugin_options as air_temp_data plugin_options['ds_name_0'] = air_temp_data['label_ds0'] plugin_options['ds_name_1'] = air_temp_data['label_ds1'] plugin_options['ds_name_2'] = air_temp_data['label_ds2'] plugin_options['ds_name_3'] = air_temp_data['label_ds3'] plugin_options['ds_name_4'] = air_temp_data['label_ds4'] plugin_options['ds_name_5'] = air_temp_data['label_ds5'] plugin_options['ds_count'] = air_temp_data['ds_used'] from plugins.air_temp_humi import DS18B20_read_probe temperature_ds = [ DS18B20_read_probe(0), DS18B20_read_probe(1), DS18B20_read_probe(2), DS18B20_read_probe(3), DS18B20_read_probe(4), DS18B20_read_probe(5) ] except: log.error( NAME, _(u'Unable to load settings from Air Temperature and Humidity Monitor plugin! Is the plugin Air Temperature and Humidity Monitor installed and set up?' )) self._sleep(60) # regulation A if plugin_options['enabled_a']: ds_a_on = temperature_ds[plugin_options['probe_A_on']] ds_a_off = temperature_ds[plugin_options['probe_A_off']] station_a = stations.get( plugin_options['control_output_A']) if ds_a_on > plugin_options[ 'temp_a_on']: # if DSxx > temperature AON a_state = 1 if msg_a_on: msg_a_on = False msg_a_off = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_a.name + ' ' + _(u'was turned on.')) start = datetime.datetime.now() sid = station_a.index end = datetime.datetime.now() + datetime.timedelta( seconds=plugin_options['reg_ss_a'], minutes=plugin_options['reg_mm_a']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _('Temperature Switch A'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if ds_a_off < plugin_options[ 'temp_a_off']: # if DSxx < temperature AOFF a_state = 0 if msg_a_off: msg_a_off = False msg_a_on = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_a.name + ' ' + _(u'was turned off.')) sid = station_a.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) else: a_state = -1 # regulation B if plugin_options['enabled_b']: ds_b_on = temperature_ds[plugin_options['probe_B_on']] ds_b_off = temperature_ds[plugin_options['probe_B_off']] station_b = stations.get( plugin_options['control_output_B']) if ds_b_on > plugin_options[ 'temp_b_on']: # if DSxx > temperature BON b_state = 1 if msg_b_on: msg_b_on = False msg_b_off = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_b.name + ' ' + _(u'was turned on.')) start = datetime.datetime.now() sid = station_b.index end = datetime.datetime.now() + datetime.timedelta( seconds=plugin_options['reg_ss_b'], minutes=plugin_options['reg_mm_b']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _('Temperature Switch B'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if ds_b_off < plugin_options[ 'temp_b_off']: # if DSxx < temperature BOFF b_state = 0 if msg_b_off: msg_b_off = False msg_b_on = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_b.name + ' ' + _(u'was turned off.')) sid = station_b.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) else: b_state = -1 # regulation C if plugin_options['enabled_c']: ds_c_on = temperature_ds[plugin_options['probe_C_on']] ds_c_off = temperature_ds[plugin_options['probe_C_off']] station_c = stations.get( plugin_options['control_output_C']) if ds_c_on > plugin_options[ 'temp_c_on']: # if DSxx > temperature CON c_state = 1 if msg_c_on: msg_c_on = False msg_c_off = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_c.name + ' ' + _(u'was turned on.')) start = datetime.datetime.now() sid = station_c.index end = datetime.datetime.now() + datetime.timedelta( seconds=plugin_options['reg_ss_c'], minutes=plugin_options['reg_mm_c']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _('Temperature Switch C'), 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': start, 'original_start': start, 'end': end, 'uid': '%s-%s-%d' % (str(start), "Manual", sid), 'usage': stations.get(sid).usage } log.start_run(new_schedule) stations.activate(new_schedule['station']) if ds_c_off < plugin_options[ 'temp_c_off']: # if DSxx < temperature COFF c_state = 0 if msg_c_off: msg_c_off = False msg_c_on = True log.info( NAME, datetime_string() + ' ' + u'%s' % station_c.name + ' ' + _(u'was turned off.')) sid = station_c.index stations.deactivate(sid) active = log.active_runs() for interval in active: if interval['station'] == sid: log.finish_run(interval) else: c_state = -1 # footer text tempText = ' ' if a_state == 0: tempText += _(u'Regulation A set OFF') + '. ' if a_state == 1: tempText += _(u'Regulation A set ON') + '. ' if b_state == 0: tempText += ' ' + _(u'Regulation B set OFF') + '. ' if b_state == 1: tempText += ' ' + _(u'Regulation B set ON') + '. ' if c_state == 0: tempText += ' ' + _(u'Regulation C set OFF') + '. ' if c_state == 1: tempText += ' ' + _(u'Regulation C set ON') + '. ' if (a_state == -1) and (b_state == -1) and (c_state == -1): tempText = _(u'No change') + '. ' if plugin_options['use_footer']: if temp_sw is not None: if tempText != helper_text: temp_sw.val = tempText.encode('utf8').decode( 'utf8') # value on footer helper_text = tempText self._sleep(2) millis = int(round(time.time() * 1000)) if (millis - last_millis ) > 60000: # 60 second to clearing status on the webpage last_millis = millis log.clear(NAME) except Exception: log.error( NAME, _(u'Temperature Switch plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)