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_station_is_on(): # return true if stations is ON if not options.manual_mode: # if not manual control for station in stations.get(): if station.active: # if station is active return True else: return False
def get_master_is_on(): if stations.master is not None or stations.master_two is not None and not options.manual_mode: # if is use master station and not manual control for station in stations.get(): if station.is_master or station.is_master_two: # if station is master if station.active: # if master is active return True else: return False
def station_names(): """ Return station names as a list. """ station_list = [] for station in stations.get(): station_list.append(station.name) return json.dumps(station_list)
def get_active_state(): station_state = False station_result = '' for station in stations.get(): # check if station runing if station.active: station_state = True # yes runing station_result += str(station.index+1) + ', ' # station number runing ex: 2, 6, 20, if station_state: return station_result else: return False
def GET(self): statuslist = [] epoch = datetime.date(1970, 1, 1) for station in stations.get(): if station.enabled and any(station.index in program.stations for program in programs.get()): statuslist.append({ 'station': station.name, 'balances': {int((key - epoch).total_seconds()): value for key, value in station.balance.iteritems()}}) web.header('Content-Type', 'application/json') return json.dumps(statuslist, indent=2)
def run(self): last_rain = False finished_count = len( [run for run in log.finished_runs() if not run['blocked']]) if email_options[ "emlpwron"]: # if eml_power_on send email is enable (on) body = (datetime_string() + ': System was powered on.') if email_options["emllog"]: self.try_mail(body, EVENT_FILE) else: self.try_mail(body) while not self._stop_event.is_set(): try: # Send E-amil if rain is detected if email_options["emlrain"]: if inputs.rain_sensed() and not last_rain: body = (datetime_string() + ': System detected rain.') self.try_mail(body) last_rain = inputs.rain_sensed() # Send E-mail if a new finished run is found if email_options["emlrun"]: finished = [ run for run in log.finished_runs() if not run['blocked'] ] if len(finished) > finished_count: body = datetime_string() + ':\n' for run in finished[finished_count:]: duration = (run['end'] - run['start']).total_seconds() minutes, seconds = divmod(duration, 60) body += "Finished run:\n" body += " Program: %s\n" % run['program_name'] body += " Station: %s\n" % stations.get( run['station']).name body += " Start time: %s \n" % datetime_string( run['start']) body += " Duration: %02d:%02d\n\n" % (minutes, seconds) self.try_mail(body) finished_count = len(finished) self._sleep(5) except Exception: log.error(NAME, 'E-mail plug-in:\n' + traceback.format_exc()) self._sleep(60)
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 notify_zone_change(name, **kw): if plugin_options['zoneChange']: txt = _('There has been a Station Change.\n') for station in stations.get(): txt += _('Station: {} State: ').format(station.name) if station.active: txt += _('ON') + u' (' if(station.remaining_seconds == -1): txt += _('Forever') + _(u')') else: txt += '{}'.format(str(int(station.remaining_seconds))) + _(')') else: txt += _('OFF') txt += '\n' sender._announce(txt)
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 on_station_clear(name, **kw): """ Send all CMD to OFF when core program signals in station state.""" log.clear(NAME) log.info(NAME, _(u'All station change to OFF')) for station in stations.get(): command = plugin_options['off'] data = command[station.index] if data: log.info( NAME, _(u'Im trying to send an OFF command: {}').format( station.index + 1)) run_command(data) else: log.info( NAME, _(u'No command is set for OFF: {}').format(station.index + 1)) return
def run(self): log.clear(NAME) log.info(NAME, 'Test started for ' + str(pulse_options['test_time']) + ' sec.') station = stations.get(pulse_options['test_output']) for x in range(0, pulse_options['test_time']): station.active = True time.sleep(0.5) station.active = False time.sleep(0.5) if self._stop_event.is_set(): break log.info(NAME, 'Test stopped.') # Activate again if needed: if station.remaining_seconds != 0: station.active = True
def clear_runs(self, all_entries=True): from ospy.programs import programs, ProgramType from ospy.stations import stations if all_entries or not options.run_log: # User request or logging is disabled minimum = 0 elif options.run_entries > 0: minimum = options.run_entries else: return # We should not prune in this case # determine the start of the first active run: first_start = min( [datetime.datetime.now()] + [interval['start'] for interval in self.active_runs()]) min_eto = datetime.date.today() + datetime.timedelta(days=1) for program in programs.get(): if program.type == ProgramType.WEEKLY_WEATHER: for station in program.stations: min_eto = min( min_eto, min([ datetime.date.today() - datetime.timedelta(days=7) ] + stations.get(station).balance.keys())) # Now try to remove as much as we can for index in reversed(xrange(len(self._log['Run']) - minimum)): interval = self._log['Run'][index]['data'] delete = True # If this entry cannot have influence on the current state anymore: if (first_start - interval['end']).total_seconds() <= max( options.station_delay + options.min_runtime, options.master_off_delay, 60): delete = False elif interval['end'].date() >= min_eto: delete = False if delete: del self._log['Run'][index] self._save_logs()
def on_zone_change(name, **kw): """ Switch relays when core program signals a change in station state.""" # command = "wget http://xxx.xxx.xxx.xxx/relay1on" log.clear(NAME) log.info(NAME, _('Zone change signaled...')) for station in stations.get(): if station.active: command = plugin_options['on'] data = command[station.index] if data: #print 'data:', data run_command(data) else: command = plugin_options['off'] data = command[station.index] if data: #print 'data:', data run_command(data) return
def _botCmd_info(self, bot, update): chat_id = update.message.chat.id if chat_id in list(self._currentChats): txt = _(u'Info from {}\n').format(options.name) for station in stations.get(): txt += _(u'Station: {} State: ').format(station.name) if station.active: txt += _(u'ON') + u' (' if(station.remaining_seconds == -1): txt += _(u'Forever') + u')' else: txt += _(u'{}').format(str(int(station.remaining_seconds))) + u')' else: txt += _(u'OFF') txt += '\n' txt += _(u'Scheduler is {}.\n').format( _(u'enabled') if options.scheduler_enabled else _(u'disabled')) else: txt = _(u'Sorry I can not do that.') if is_python2(): bot.sendMessage(chat_id, text=txt.encode('utf-8')) else: bot.sendMessage(chat_id, text=txt)
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 run(self): log.clear(NAME) log.info(NAME, datetime_string() + ' ' + _(u'Started for {} seconds.').format(plugin_options['open_time'])) start = datetime.datetime.now() sid = int(plugin_options['open_output']) end = datetime.datetime.now() + datetime.timedelta(seconds=plugin_options['open_time']) new_schedule = { 'active': True, 'program': -1, 'station': sid, 'program_name': _(u'Door Opening'), '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'])
def update_station_schedule(self): if self.type != ProgramType.WEEKLY_WEATHER: self._station_schedule = {} for station in self.stations: self._station_schedule[station] = self._schedule else: now = datetime.datetime.now() week_start = datetime.datetime.combine( now.date() - datetime.timedelta(days=now.weekday()), datetime.time.min) last_start = self._start self._start = week_start start_difference = int( round((week_start - last_start).total_seconds() / 60)) irrigation_min, irrigation_max, run_max, pause_ratio, pem_mins = self.type_data try: pems = [(week_start + datetime.timedelta(minutes=x), y) for x, y in pem_mins] pems += [(week_start + datetime.timedelta(days=7, minutes=x), y) for x, y in pem_mins] pems += [(week_start + datetime.timedelta(days=-7, minutes=x), y) for x, y in pem_mins] pems = sorted(pems) pems = [ x for x in pems if x[0] >= now - datetime.timedelta(hours=1) ] pems = [ x for x in pems if (x[0].date() - now.date()).days < 10 ] to_sprinkle = {} for station in self.stations: to_sprinkle[station] = [] station_pems = pems[:] # Make sure to keep whatever we were planning to do if station in self._station_schedule: for interval in self._station_schedule[station]: if now - datetime.timedelta( hours=1) < last_start + datetime.timedelta( minutes=interval[1] ) and last_start + datetime.timedelta( minutes=interval[0] ) < now + datetime.timedelta(hours=1): to_sprinkle[station].append([ interval[0] + start_difference, interval[1] + start_difference ]) elif to_sprinkle[ station] and last_start + datetime.timedelta( minutes=interval[0] ) - (week_start + datetime.timedelta( minutes=to_sprinkle[station][-1][1]) ) < datetime.timedelta(hours=3): to_sprinkle[station].append([ interval[0] + start_difference, interval[1] + start_difference ]) if to_sprinkle[station]: station_pems = [ x for x in pems if x[0] > week_start + datetime.timedelta( minutes=to_sprinkle[station][-1][1]) ] station_balance = { -1: stations.get(station).balance[now.date() - datetime.timedelta( days=1)]['total'] } rain = { -1: stations.get(station).balance[now.date() - datetime.timedelta( days=1)]['rain'] } for day_index in range(0, 10): overall_balance = stations.get(station).balance[ now.date() + datetime.timedelta(days=day_index)] station_balance[day_index] = station_balance[day_index-1] \ - overall_balance['eto'] \ + overall_balance['rain'] \ + sum(interval['irrigation'] for interval in overall_balance['intervals'] if interval['done'] or interval['program'] != self.index) station_balance[day_index] = max( -100, min(station_balance[day_index], stations.get(station).capacity)) rain[day_index] = overall_balance['rain'] for index, (pem, prio) in enumerate(station_pems): day_index = (pem.date() - now.date()).days rain_today = max(rain[max(-1, day_index - 1)], rain[day_index], rain[min(day_index + 1, 9)]) better_days = [ x for x in station_pems[index + 1:] if x[1] > prio ] better_or_equal_days = [ x for x in station_pems[index + 1:] if x[1] >= prio and x[0] > pem ] any_days = station_pems[index + 1:] target_index, target_index_pref = 9, 9 if any_days: target_index = (any_days[0][0].date() - now.date()).days if not better_days: # The best day: amount = irrigation_max if better_or_equal_days: target_index_pref = ( better_or_equal_days[0][0].date() - now.date()).days else: # A better day is possible: amount = 0 target_index_pref = (better_days[0][0].date() - now.date()).days # Make sure not to overflow the capacity (and aim for 0 for today): later_sprinkle_max = min( [-station_balance[day_index]] + [ stations.get(station).capacity - station_balance[later_day_index] for later_day_index in range( day_index + 1, target_index_pref) ]) # Make sure we sprinkle enough not to go above the maximum in the future: later_sprinkle_min = max( -station_balance[later_day_index] - irrigation_max for later_day_index in range( day_index, target_index + 1)) if later_sprinkle_min > 0: # We need to do something to prevent going over the maximum, so: later_sprinkle_min = max( irrigation_min, later_sprinkle_min) # Make sure to sprinkle # Try to go towards a better day: later_sprinkle_min_pref = max( -station_balance[later_day_index] - irrigation_max for later_day_index in range( day_index, target_index_pref + 1)) if later_sprinkle_min_pref > 0: # We need to do something to prevent going over the maximum, so: later_sprinkle_min_pref = max( irrigation_min, later_sprinkle_min) # Make sure to sprinkle # Calculate the final value based on the constraints that we have: # print station, pem, amount, later_sprinkle_min, later_sprinkle_min_pref, later_sprinkle_max, irrigation_max-rain_today, irrigation_max, stations.get(station).capacity, [-station_balance[day_index]] + [(stations.get(station).capacity - station_balance[later_day_index]) for later_day_index in range(day_index+1, target_index_pref)] amount = min( max( later_sprinkle_min, min(max(later_sprinkle_min_pref, amount), later_sprinkle_max, irrigation_max - rain_today)), irrigation_max) if amount >= irrigation_min: logging.debug( 'Weather based schedule for %s: PEM: %s, priority: %s, amount: %f.', stations.get(station).name, str(pem), prio, amount) for later_day_index in range(day_index, 10): station_balance[later_day_index] += amount week_min = (pem - week_start).total_seconds() / 60 intervals = [amount] while any(x > run_max for x in intervals): new_len = len(intervals) + 1 intervals = [amount / new_len] * new_len for interval in intervals: station_duration = int( round(interval * 60 / stations.get(station).precipitation)) to_sprinkle[station] = self._update_schedule( to_sprinkle[station], self.modulo, week_min, week_min + station_duration) week_min += station_duration + int( round(station_duration * pause_ratio)) logging.debug( 'Weather based deficit for %s: %s', stations.get(station).name, str( sorted([((now.date() + datetime.timedelta(days=x)).isoformat(), y) for x, y in station_balance.iteritems()]))) self._station_schedule = to_sprinkle except Exception: logging.warning('Could not create weather based schedule:\n' + traceback.format_exc())
def run(self): time.sleep( randint(3, 10) ) # Sleep some time to prevent printing before startup information send_interval = 10000 # default time for sending between e-mails (ms) last_millis = 0 # timer for repeating sending e-mails (ms) last_rain = False body = u'' logtext = u'' finished_count = len([run for run in log.finished_runs() if not run['blocked']]) if email_options["emlpwron"]: # if eml_power_on send email is enable (on) body += u'<b>' + _(u'System') + u'</b> ' + datetime_string() body += u'<br><p style="color:red;">' + _(u'System was powered on.') + u'</p>' logtext = _(u'System was powered on.') if email_options["emllog"]: file_exists = os.path.exists(EVENT_FILE) if file_exists: try_mail(body, logtext, EVENT_FILE) else: body += u'<br>' + _(u'Error - events.log file not exists!') try_mail(body, logtext) else: try_mail(body, logtext) while not self._stop_event.is_set(): body = u'' logtext = u'' try: # Send E-amil if rain is detected if email_options["emlrain"]: if inputs.rain_sensed() and not last_rain: body += u'<b>' + _(u'System') + u'</b> ' + datetime_string() body += u'<br><p style="color:red;">' + _(u'System detected rain.') + u'</p><br>' logtext = _(u'System detected rain.') try_mail(body, logtext) last_rain = inputs.rain_sensed() # Send E-mail if a new finished run is found if email_options["emlrun"]: finished = [run for run in log.finished_runs() if not run['blocked']] if len(finished) > finished_count: for run in finished[finished_count:]: duration = (run['end'] - run['start']).total_seconds() minutes, seconds = divmod(duration, 60) pname = run['program_name'] sname = stations.get(run['station']).name body += u'<br>' body += u'<b>' + _(u'System') + u'</b> ' + datetime_string() body += u'<br><b>' + _(u'Finished run') + u'</b>' body += u'<ul><li>' + _(u'Program') + u': %s \n' % pname + u'</li>' body += u'<li>' + _(u'Station') + u': %s \n' % sname + u'</li>' body += u'<li>' + _(u'Start time') + u': %s \n' % datetime_string(run['start']) + u'</li>' body += u'<li>' + _(u'Duration') + u': %02d:%02d\n' % (minutes, seconds) + u'</li></ul>' logtext = _(u'Finished run') + u'-> \n' + _(u'Program') + u': %s\n' % pname logtext += _(u'Station') + u': %s\n' % sname logtext += _(u'Start time') + u': %s \n' % datetime_string(run['start']) logtext += _(u'Duration') + u': %02d:%02d\n' % (minutes, seconds) # Water Tank Monitor try: from plugins import tank_monitor cm = tank_monitor.get_all_values()[0] percent = tank_monitor.get_all_values()[1] ping = tank_monitor.get_all_values()[2] volume = tank_monitor.get_all_values()[3] units = tank_monitor.get_all_values()[4] msg = ' ' if cm > 0: msg = _(u'Level') + u': ' + str(cm) + u' ' + _(u'cm') msg += u' (' + str(percent) + u' %), ' msg += _(u'Ping') + u': ' + str(ping) + u' ' + _(u'cm') if units: msg += u', ' + _(u'Volume') + u': ' + str(volume) + u' ' + _(u'liters') else: msg += u', ' + _(u'Volume') + u': ' + str(volume) + u' ' + _(u'm3') else: msg = _(u'Error - I2C device not found!') body += u'<b>' + _(u'Water') + u'</b>' body += u'<br><ul><li>' + _(u'Water level in tank') + u': %s \n</li></ul>' % (msg) logtext += _(u'Water') + u'-> \n' + _(u'Water level in tank') + u': %s \n' % (msg) except ImportError: log.debug(NAME, _(u'Cannot import plugin: tank monitor.')) pass # Water Consumption Counter try: from plugins import water_consumption_counter self._sleep(2) # wait for the meter to save consumption consum_from = water_consumption_counter.get_all_values()[0] consum_one = float(water_consumption_counter.get_all_values()[1]) consum_two = float(water_consumption_counter.get_all_values()[2]) msg = u' ' msg += _(u'Measured from day') + u': ' + str(consum_from) + u', ' msg += _(u'Master Station') + u': ' if consum_one < 1000.0: msg += str(consum_one) + u' ' msg += _(u'Liter') + u', ' else: msg += str(round((consum_one/1000.0), 2)) + u' ' msg += _(u'm3') + ', ' msg += _(u'Second Master Station') + u': ' if consum_two < 1000.0: msg += str(consum_two) + u' ' msg += _(u'Liter') else: msg += str(round((consum_two/1000.0), 2)) + u' ' msg += _(u'm3') body += u'<br><b>' + _(u'Water Consumption Counter') + u'</b>' body += u'<br><ul><li>%s \n</li></ul>' % (msg) logtext += _(u'Water Consumption Counter') + u': %s \n' % (msg) except ImportError: log.debug(NAME, _(u'Cannot import plugin: water consumption counter.')) pass # Air Temperature and Humidity Monitor try: from plugins import air_temp_humi body += u'<br><b>' + _(u'Temperature DS1-DS6') + u'</b><ul>' logtext += _(u'Temperature DS1-DS6') + u'-> \n' for i in range(0, air_temp_humi.plugin_options['ds_used']): body += u'<li>' + u'%s' % air_temp_humi.plugin_options['label_ds%d' % i] + u': ' + u'%.1f \u2103' % air_temp_humi.DS18B20_read_probe(i) + u'\n</li>' logtext += u'%s' % air_temp_humi.plugin_options['label_ds%d' % i] + u': ' + u'%.1f \u2103\n' % air_temp_humi.DS18B20_read_probe(i) body += u'</ul>' except ImportError: log.debug(NAME, _(u'Cannot import plugin: air temp humi.')) pass # OSPy Sensors try: body += u'<br><b>' + _(u'Sensors') + u'</b>' logtext += _(u'Sensors') + u'-> \n' sensor_result = '' if sensors.count() > 0: body += u'<ul>' for sensor in sensors.get(): sensor_result = '' body += u'<li>' sensor_result += u'{}: '.format(sensor.name) if sensor.enabled: if sensor.response == 1: if sensor.sens_type == 1: # dry contact if sensor.last_read_value[4] == 1: sensor_result += _(u'Contact Closed') elif sensor.last_read_value[4] == 0: sensor_result += _(u'Contact Open') else: sensor_result += _(u'Probe Error') if sensor.sens_type == 2: # leak detector if sensor.last_read_value[5] != -127: sensor_result += str(sensor.last_read_value[5]) + ' ' + _(u'l/s') else: sensor_result += _(u'Probe Error') if sensor.sens_type == 3: # moisture if sensor.last_read_value[6] != -127: sensor_result += str(sensor.last_read_value[6]) + ' ' + _(u'%') else: sensor_result += _(u'Probe Error') if sensor.sens_type == 4: # motion if sensor.last_read_value[7] != -127: sensor_result += _(u'Motion Detected') if int(sensor.last_read_value[7]) == 1 else _(u'No Motion') else: sensor_result += _(u'Probe Error') if sensor.sens_type == 5: # temperature if sensor.last_read_value[0] != -127: sensor_result += u'%.1f \u2103' % sensor.last_read_value[0] else: sensor_result += _(u'Probe Error') if sensor.sens_type == 6: # multi sensor if sensor.multi_type >= 0 and sensor.multi_type < 4:# multi temperature DS1-DS4 if sensor.last_read_value[sensor.multi_type] != -127: sensor_result += u'%.1f \u2103' % sensor.last_read_value[sensor.multi_type] else: sensor_result += _(u'Probe Error') if sensor.multi_type == 4: # multi dry contact if sensor.last_read_value[4] != -127: sensor_result += _(u'Contact Closed') if int(sensor.last_read_value[4]) == 1 else _(u'Contact Open') else: sensor_result += _(u'Probe Error') if sensor.multi_type == 5: # multi leak detector if sensor.last_read_value[5] != -127: sensor_result += str(sensor.last_read_value[5]) + ' ' + _(u'l/s') else: sensor_result += _(u'Probe Error') if sensor.multi_type == 6: # multi moisture if sensor.last_read_value[6] != -127: sensor_result += str(sensor.last_read_value[6]) + ' ' + _(u'%') else: sensor_result += _(u'Probe Error') if sensor.multi_type == 7: # multi motion if sensor.last_read_value[7] != -127: sensor_result += _(u'Motion Detected') if int(sensor.last_read_value[7])==1 else _(u'No Motion') else: sensor_result += _(u'Probe Error') if sensor.multi_type == 8: # multi ultrasonic if sensor.last_read_value[8] != -127: get_level = get_tank_cm(sensor.last_read_value[8], sensor.distance_bottom, sensor.distance_top) get_perc = get_percent(get_level, sensor.distance_bottom, sensor.distance_top) sensor_result += u'{} '.format(get_level) + _(u'cm') + u' ({} %)'.format(get_perc) else: sensor_result += _(u'Probe Error') if sensor.multi_type == 9: # multi soil moisture err_check = 0 calculate_soil = [0.0]*16 state = [-127]*16 for i in range(0, 16): if type(sensor.soil_last_read_value[i]) == float: state[i] = sensor.soil_last_read_value[i] ### voltage from probe to humidity 0-100% with calibration range (for footer info) if sensor.soil_invert_probe_in[i]: # probe: rotated state 0V=100%, 3V=0% humidity calculate_soil[i] = maping(state[i], float(sensor.soil_calibration_max[i]), float(sensor.soil_calibration_min[i]), 0.0, 100.0) calculate_soil[i] = round(calculate_soil[i], 1) # round to one decimal point calculate_soil[i] = 100.0 - calculate_soil[i] # ex: 90% - 90% = 10%, 10% is output in invert probe mode else: # probe: normal state 0V=0%, 3V=100% calculate_soil[i] = maping(state[i], float(sensor.soil_calibration_min[i]), float(sensor.soil_calibration_max[i]), 0.0, 100.0) calculate_soil[i] = round(calculate_soil[i], 1) # round to one decimal point if state[i] > 0.1: if sensor.soil_show_in_footer[i]: sensor_result += '{} {}% ({}V) '.format(sensor.soil_probe_label[i], round(calculate_soil[i], 2), round(state[i], 2)) else: err_check += 1 if err_check > 15: sensor_result += _(u'Probe Error') if sensor.com_type == 0: # Wi-Fi/LAN sensor_result += u' ' + _(u'Last Wi-Fi signal: {}%, Source: {}V.').format(sensor.rssi, sensor.last_battery) if sensor.com_type == 1: # Radio sensor_result += u' ' + _(u'Last Radio signal: {}%, Source: {}V.').format(sensor.rssi, sensor.last_battery) else: sensor_result += _(u'No response!') else: sensor_result += _(u'Disabled') body += sensor_result body += u'</li>' logtext += sensor_result logtext += u'\n' body += u'</ul>' body += u'<br>' else: sensor_result += _(u'No sensors available') body += u'<ul><li>' body += sensor_result body += u'</li></ul>' body += u'<br>' logtext += sensor_result logtext += u'\n' except: log.debug(NAME, _(u'E-mail plug-in') + ':\n' + traceback.format_exc()) pass try_mail(body, logtext) finished_count = len(finished) ###Repeating sending e-mails### if email_options["emlrepeater"]: # saving e-mails is enabled try: millis = int(round(time.time() * 1000)) # actual time in ms if(millis - last_millis) > send_interval: # sending timer last_millis = millis # save actual time ms try: # exists file: saved_emails.json? saved_emails = read_saved_emails() # read from file except: # no! create empty file write_email([]) # create file saved_emails = read_saved_emails() # read from file len_saved_emails = len(saved_emails) if len_saved_emails > 0: # if there is something in json log.clear(NAME) log.info(NAME, _(u'Unsent E-mails in queue (in file)') + ': ' + str(len_saved_emails)) try: # try send e-mail sendtext = u'%s' % saved_emails[0]["text"] sendsubject = u'%s' % (saved_emails[0]["subject"] + '-' + _(u'sending from queue.')) sendattachment = u'%s' % saved_emails[0]["attachment"] email(sendtext, sendsubject, sendattachment) # send e-mail send_interval = 10000 # repetition of 10 seconds del saved_emails[0] # delete sent email in file write_email(saved_emails) # save to file after deleting an item if len(saved_emails) == 0: log.clear(NAME) log.info(NAME, _(u'All unsent E-mails in the queue have been sent.')) except Exception: #print traceback.format_exc() send_interval = 60000 # repetition of 60 seconds except: log.error(NAME, _(u'E-mail plug-in') + ':\n' + traceback.format_exc()) self._sleep(2) except Exception: log.error(NAME, _(u'E-mail plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
def FTP_upload(self): try: # create a file "stavy.php" cas = time.strftime('%d.%m.%Y (%H:%M:%S)', time.localtime(time.time())) text = "<?php\r\n$cas = \"" # actual time (ex: cas = dd.mm.yyyy (HH:MM:SS) text += cas + "\";\r\n" text += "$rain = \'" # rain state (ex: rain = 0/1) text += str(1 if inputs.rain_sensed() else 0) + "\';\r\n" text += "$output = \'" # use xx outputs count (ex: output = 8) text += str(options.output_count) + "\';\r\n" text += "$program = \'" # use xx programs count (ex: program = 3) text += str(programs.count()) + "\';\r\n" text += "$masterstat = " # master stations index for station in stations.get(): if station.is_master: text += "\'" + str(station.index) + "\';\r\n" if stations.master is None: text += "\'\';\r\n" text += "$raindel = " # rain delay if rain_blocks.seconds_left(): m, s = divmod(int(rain_blocks.seconds_left()), 60) h, m = divmod(m, 60) text += "\'" + "%dh:%02dm:%02ds" % (h, m, s) + "\';\r\n" else: text += "\'\';\r\n" import unicodedata # for only asci text in PHP WEB (stations name, program name...) namestations = [] for num in range(0, options.output_count): # stations name as array namestations.append( unicodedata.normalize('NFKD', stations.get(num).name).encode( 'ascii', 'ignore')) text += "$name" + " = array" text += str(namestations) + ";\r\n" statestations = [] for num in range(0, options.output_count): # stations state as array statestations.append(1 if stations.get(num).active else 0) text += "$state" + " = array" text += str(statestations) + ";\r\n" progrname = [] for program in programs.get(): # program name as array progrname.append( unicodedata.normalize('NFKD', program.name).encode('ascii', 'ignore')) text += "$progname" + " = array" text += str(progrname) + ";\r\n" text = text.replace('[', '(') text = text.replace(']', ')') text += "$schedul = \'" # scheduller state (ex: schedul = 0 manual/ 1 scheduler) text += str(0 if options.manual_mode else 1) + "\';\r\n" text += "$system = \'" # scheduller state (ex: system = 0 stop/ 1 scheduler) text += str(0 if options.scheduler_enabled else 1) + "\';\r\n" text += "$cpu = \'" # cpu temp (ex: cpu = 45.2) text += str(helpers.get_cpu_temp(options.temp_unit)) + "\';\r\n" text += "$unit = \'" # cpu temp unit(ex: unit = C/F) in Celsius or Fahrenheit text += str(options.temp_unit) + "\';\r\n" text += "$ver = \'" # software version date (ex: ver = 2016-07-30) text += str(version.ver_date) + "\';\r\n" text += "$id = \'" # software OSPy ID (ex: id = "opensprinkler") text += str(options.name) + "\';\r\n" text += "$ip = \'" # OSPy IP address (ex: ip = "192.168.1.1") text += str(helpers.get_ip()) + "\';\r\n" text += "$port = \'" # OSPy port (ex: port = "8080") text += str(options.web_port) + "\';\r\n" text += "$ssl = \'" # OSPy use ssl port (ex: ssl = "1" or "0") text += str(1 if options.use_ssl else 0) + "\';\r\n" tank = '' try: from plugins import tank_humi_monitor tank = tank_humi_monitor.get_sonic_tank_cm() if tank < 0: # -1 is error I2C device for ping not found in tank_humi_monitor tank = '' except Exception: tank = '' text = text + "$tank = \'" # from tank humi monitor plugins check water level (ex: tank = "100" in cm or "") text = text + str(tank) + "\';\r\n" press = '' try: from plugins import pressure_monitor press = pressure_monitor.get_check_pressure() except Exception: press = '' text = text + "$press = \'" # from pressure plugins check press (ex: press = "1" or "0") text = text + str(press) + "\';\r\n" ups = '' try: from plugins import ups_adj ups = ups_adj.get_check_power( ) # read state power line from plugin ups adj except Exception: ups = '' text = text + "$ups = \'" text = text + str(ups) + "\';\r\n" result = '' finished = [run for run in log.finished_runs() if not run['blocked']] if finished: result = finished[-1]['start'].strftime( '%d-%m-%Y v %H:%M:%S program: ') + finished[-1]['program_name'] else: result = '' text = text + "$lastrun = \'" # last run program (ex: start d-m-r v h:m:s program: jmemo programu) text = text + str(result) + "\';\r\n" text = text + "$up = \'" # system run uptime text = text + str(helpers.uptime()) + "\';\r\n" text = text + "?>\r\n" #print text """ example php code ----------- <?php $cas = "09.08.2016 (15:28:10)"; $rain = '0'; $output = '3'; $program = '3'; $masterstat = '0'; $raindel = '1:26:22'; // hod:min:sec $name = array('cerpadlo','test','out2'); $state = array('0', '0', '0'); $progname = array('cerpadlo v 5','test','out2'); $schedul = '1'; $system = '1'; $cpu = '40.1'; $unit = 'C'; $ver = '2016-07-30'; $id = 'Zalevac chata'; $ip = '192.168.1.253'; $port = '80'; $ssl = '1'; $tank = '20'; $press = '0'; $ups = '1'; $lastrun = '09.08.2016 v 15:28:10 program: bezi 5 minut)'; $up = '6 days, 0:48:01' ?> ------------------------------- """ try: fs = file("/home/pi/ramdisk/stavy.php", 'w') fs.write(text) fs.close() except: log.error(NAME, _(u'Could not save stavy.php to ramdisk!')) pass self.ftp.storbinary("STOR " + plugin_options['loc'] + 'stavy.php', open("/home/pi/ramdisk/stavy.php", 'rb')) log.info( NAME, _(u'Data file stavy.php has send on to FTP server') + ': ' + str(cas)) self.ftp.storbinary("STOR " + plugin_options['loc'] + 'data.txt', open("/home/pi/ramdisk/data.txt", 'rb')) log.info( NAME, _(u'Data file data.txt has send on to FTP server') + ': ' + str(cas)) self.ftp.close # FTP end except Exception: log.info( NAME, _(u'Remote FTP control settings') + ':\n' + traceback.format_exc())
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 stop_onrain(): """Stop stations that do not ignore rain.""" from ospy.stations import stations for station in stations.get(): if not station.ignore_rain: station.activated = False
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 clear_runs(self, all_entries=True): from ospy.programs import programs, ProgramType from ospy.stations import stations if all_entries or not options.run_log: # User request or logging is disabled minimum = 0 elif options.run_entries > 0: minimum = options.run_entries else: return # We should not prune in this case # determine the start of the first active run: first_start = min([datetime.datetime.now()] + [interval['start'] for interval in self.active_runs()]) min_eto = datetime.date.today() + datetime.timedelta(days=1) for program in programs.get(): if program.type == ProgramType.WEEKLY_WEATHER: for station in program.stations: min_eto = min(min_eto, min([datetime.date.today() - datetime.timedelta(days=7)] + stations.get(station).balance.keys())) # Now try to remove as much as we can for index in reversed(xrange(len(self._log['Run']) - minimum)): interval = self._log['Run'][index]['data'] delete = True # If this entry cannot have influence on the current state anymore: if (first_start - interval['end']).total_seconds() <= max(options.station_delay + options.min_runtime, options.master_off_delay, 60): delete = False elif interval['end'].date() >= min_eto: delete = False if delete: del self._log['Run'][index] self._save_logs()
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): send_msg = False # send get data if change (rain, end program .... last_rain = False en_rain = True en_line = True en_line2 = True rain = 0 # rain status for rain=0 or rain=1 in get data lastrun = "" # date and time for lastrun=xxxxx in get data tank = "" # actual level cm in water tank percent = "" # actual level % in water tank ping = "" # actual level ping cm water level volume = "" # actual level volume in m3 water level duration = "" # duration in last program for duration=xx:yy in get data station = "" # name end station for station=abcde in get data humi = "" # humidity in station for humi=xx in get data line = "" # actual state from UPS plugin for line=0 or line=1 in get data temp1 = "" # temperature 1 from air temp plugin DS18B20 temp2 = "" # temperature 2 from air temp plugin DS18B20 temp3 = "" # temperature 3 from air temp plugin DS18B20 temp4 = "" # temperature 4 from air temp plugin DS18B20 temp5 = "" # temperature 5 from air temp plugin DS18B20 temp6 = "" # temperature 6 from air temp plugin DS18B20 tempDHT = "" # temperature from air temp plugin DHT probe humiDHT = "" # humidity from air temp plugin DHT probe finished_count = len( [run for run in log.finished_runs() if not run['blocked']]) while not self._stop_event.is_set(): try: # Send data if rain detected, power line state a new finished run is found if remote_options["use"]: ### water tank level ### try: from plugins import tank_monitor tank = tank_monitor.get_all_values()[0] percent = tank_monitor.get_all_values()[1] ping = tank_monitor.get_all_values()[2] volume = tank_monitor.get_all_values()[3] except Exception: tank = "" percent = "" ping = "" volume = "" ### power line state ### try: from plugins import ups_adj lin = ups_adj.get_check_power( ) # read state power line from plugin if lin == 1: if en_line: # send only if change send_msg = True en_line = False en_line2 = True line = 0 # no power on web if lin == 0: if en_line2: # send only if change send_msg = True en_line2 = False en_line = True line = 1 # power ok on web except Exception: line = "" ### rain state ### if inputs.rain_sensed() and not last_rain: send_msg = True last_rain = inputs.rain_sensed() if inputs.rain_sensed(): rain = 1 en_rain = True else: rain = 0 if en_rain: # send data if no rain (only if change rain/norain...) send_msg = True en_rain = False if not options.rain_sensor_enabled: # if rain sensor not used rain = "" ### program and station ### finished = [ run for run in log.finished_runs() if not run['blocked'] ] if len(finished) > finished_count: las = datetime_string() lastrun = re.sub( " ", "_", las) # eliminate gap in the title to _ send_msg = True ### humidity in station ### try: from plugins import humi_monitor humi = int( humi_monitor.get_humidity( (stations.get(run['station']).index) + 1)) # 0-7 to 1-8 humidity if humi < 0: humi = "" except Exception: humi = "" ### temperature ### try: from plugins import air_temp_humi temp1 = air_temp_humi.DS18B20_read_probe(0) temp2 = air_temp_humi.DS18B20_read_probe(1) temp3 = air_temp_humi.DS18B20_read_probe(2) temp4 = air_temp_humi.DS18B20_read_probe(3) temp5 = air_temp_humi.DS18B20_read_probe(4) temp6 = air_temp_humi.DS18B20_read_probe(5) tempDHT = air_temp_humi.DHT_read_temp_value() humiDHT = air_temp_humi.DHT_read_humi_value() except Exception: temp1 = "" temp2 = "" temp3 = "" temp4 = "" temp5 = "" temp6 = "" tempDHT = "" humiDHT = "" for run in finished[finished_count:]: dur = (run['end'] - run['start']).total_seconds() minutes, seconds = divmod(dur, 60) sta = "%s" % stations.get(run['station']).name station = re.sub( " ", "_", sta) # eliminate gap in the title to _ duration = "%02d:%02d" % (minutes, seconds) finished_count = len(finished) if (send_msg): # if enabled send data body = ('tank=' + str(tank)) body += ('&percent=' + str(percent)) body += ('&ping=' + str(ping)) body += ('&volume=' + str(volume)) body += ('&rain=' + str(rain)) body += ('&humi=' + str(humi)) body += ('&line=' + str(line)) body += ('&lastrun=' + str(lastrun)) body += ('&station=' + sanity_msg(station)) body += ('&duration=' + str(duration)) body += ('&temp1=' + str(temp1)) body += ('&temp2=' + str(temp2)) body += ('&temp3=' + str(temp3)) body += ('&temp4=' + str(temp4)) body += ('&temp5=' + str(temp5)) body += ('&temp6=' + str(temp6)) body += ('&tempDHT=' + str(tempDHT)) body += ('&humiDHT=' + str(humiDHT)) body += ('&api=' + remote_options['api']) # API password log.clear(NAME) log.info(NAME, _(u'Test data...')) self.try_send(body) # Send GET data to remote server send_msg = False # Disable send data self._sleep(2) except Exception: log.error( NAME, _(u'Remote plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
def run(self): last_rain = False finished_count = len( [run for run in log.finished_runs() if not run['blocked']]) if email_options[ "emlpwron"]: # if eml_power_on send email is enable (on) body = (datetime_string() + ': ' + _('System was powered on.')) if email_options["emllog"]: file_exists = os.path.exists(EVENT_FILE) if file_exists: self.try_mail(body, EVENT_FILE) else: body += '\n' + _('Error - events.log file not exists!') print body self.try_mail(body) else: self.try_mail(body) while not self._stop.is_set(): try: # Send E-amil if rain is detected if email_options["emlrain"]: if inputs.rain_sensed() and not last_rain: body = (datetime_string() + ': ' + _('System detected rain.')) self.try_mail(body) last_rain = inputs.rain_sensed() # Send E-mail if a new finished run is found if email_options["emlrun"]: finished = [ run for run in log.finished_runs() if not run['blocked'] ] if len(finished) > finished_count: body = datetime_string() + ':\n' for run in finished[finished_count:]: duration = (run['end'] - run['start']).total_seconds() minutes, seconds = divmod(duration, 60) cm = None try: from plugins import tank_humi_monitor cm = tank_humi_monitor.get_sonic_tank_cm() if cm > 0: cm = str(cm) + " cm" else: cm = _('Error - I2C device not found!') except Exception: cm = _('Not available') body += _('Finished run') + ':\n' body += '<br>' + _( 'Program') + ': %s\n' % run['program_name'] body += '<br>' + _( 'Station') + ': %s\n' % stations.get( run['station']).name body += '<br>' + _( 'Start time') + ': %s \n' % datetime_string( run['start']) body += '<br>' + _( 'Duration') + ': %02d:%02d\n\n' % (minutes, seconds) body += '<br>' + _( 'Water level in tank') + ': %s \n\n' % (cm) self.try_mail(body) self._sleep(3) finished_count = len(finished) self._sleep(5) except Exception: log.error(NAME, _('E-mail plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
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 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 update_station_schedule(self): if self.type != ProgramType.WEEKLY_WEATHER: self._station_schedule = {} for station in self.stations: self._station_schedule[station] = self._schedule else: now = datetime.datetime.now() week_start = datetime.datetime.combine(now.date() - datetime.timedelta(days=now.weekday()), datetime.time.min) last_start = self._start self._start = week_start start_difference = int(round((week_start - last_start).total_seconds() / 60)) irrigation_min, irrigation_max, run_max, pause_ratio, pem_mins = self.type_data try: pems = [(week_start + datetime.timedelta(minutes=x), y) for x, y in pem_mins] pems += [(week_start + datetime.timedelta(days=7, minutes=x), y) for x, y in pem_mins] pems += [(week_start + datetime.timedelta(days=-7, minutes=x), y) for x, y in pem_mins] pems = sorted(pems) pems = [x for x in pems if x[0] >= now - datetime.timedelta(hours=3)] pems = [x for x in pems if (x[0].date() - now.date()).days < 7] to_sprinkle = {} for station in self.stations: to_sprinkle[station] = [] station_pems = pems[:] # Make sure to keep whatever we were planning to do if station in self._station_schedule: for interval in self._station_schedule[station]: if now - datetime.timedelta(hours=3) < last_start + datetime.timedelta(minutes=interval[1]) and last_start + datetime.timedelta(minutes=interval[0]) < now + datetime.timedelta(hours=1): to_sprinkle[station].append([interval[0] + start_difference, interval[1] + start_difference]) elif to_sprinkle[station] and last_start + datetime.timedelta(minutes=interval[0]) - (week_start + datetime.timedelta(minutes=to_sprinkle[station][-1][1])) < datetime.timedelta(hours=3): to_sprinkle[station].append([interval[0] + start_difference, interval[1] + start_difference]) if to_sprinkle[station]: station_pems = [x for x in pems if x[0] > week_start + datetime.timedelta(minutes=to_sprinkle[station][-1][1])] station_balance = { -1: stations.get(station).balance[now.date() - datetime.timedelta(days=1)]['total'] } rain = { -1: stations.get(station).balance[now.date() - datetime.timedelta(days=1)]['rain'] } for day_index in range(0, 7): overall_balance = stations.get(station).balance[now.date() + datetime.timedelta(days=day_index)] station_balance[day_index] = station_balance[day_index-1] \ - overall_balance['eto'] \ + overall_balance['rain'] \ + sum(interval['irrigation'] for interval in overall_balance['intervals'] if interval['done'] or interval['program'] != self.index) station_balance[day_index] = max(-100, min(station_balance[day_index], stations.get(station).capacity)) rain[day_index] = overall_balance['rain'] for index, (pem, prio) in enumerate(station_pems): day_index = (pem.date() - now.date()).days rain_today = max(rain[max(-1, day_index-1)], rain[day_index], rain[min(day_index+1, 6)]) better_days = [x for x in station_pems[index+1:] if x[1] > prio] better_or_equal_days = [x for x in station_pems[index+1:] if x[1] >= prio and x[0] > pem] any_days = station_pems[index+1:] target_index, target_index_pref = 6, 6 if any_days: target_index = (any_days[0][0].date() - now.date()).days if not better_days: # The best day: amount = irrigation_max if better_or_equal_days: target_index_pref = (better_or_equal_days[0][0].date() - now.date()).days else: # A better day is possible: amount = 0 target_index_pref = (better_days[0][0].date() - now.date()).days # Make sure not to overflow the capacity (and aim for 0 for today): later_sprinkle_max = min([-station_balance[day_index]] + [stations.get(station).capacity - station_balance[later_day_index] for later_day_index in range(day_index+1, target_index_pref)]) # Make sure we sprinkle enough not to go above the maximum in the future: later_sprinkle_min = max(-station_balance[later_day_index] - irrigation_max for later_day_index in range(day_index, target_index + 1)) if later_sprinkle_min > 0: # We need to do something to prevent going over the maximum, so: later_sprinkle_min = max(irrigation_min, later_sprinkle_min) # Make sure to sprinkle # Try to go towards a better day: later_sprinkle_min_pref = max(-station_balance[later_day_index] - irrigation_max for later_day_index in range(day_index, target_index_pref + 1)) if later_sprinkle_min_pref > 0: # We need to do something to prevent going over the maximum, so: later_sprinkle_min_pref = max(irrigation_min, later_sprinkle_min) # Make sure to sprinkle # Calculate the final value based on the constraints that we have: # print station, pem, amount, later_sprinkle_min, later_sprinkle_min_pref, later_sprinkle_max, irrigation_max-rain_today, irrigation_max, stations.get(station).capacity, [-station_balance[day_index]] + [(stations.get(station).capacity - station_balance[later_day_index]) for later_day_index in range(day_index+1, target_index_pref)] amount = min(max(later_sprinkle_min, min(max(later_sprinkle_min_pref, amount), later_sprinkle_max, irrigation_max-rain_today)), irrigation_max) if amount >= irrigation_min: logging.debug('Weather based schedule for %s: PEM: %s, priority: %s, amount: %f.', stations.get(station).name, str(pem), prio, amount) for later_day_index in range(day_index, 7): station_balance[later_day_index] += amount week_min = (pem - week_start).total_seconds() / 60 intervals = [amount] while any(x > run_max for x in intervals): new_len = len(intervals) + 1 intervals = [amount / new_len] * new_len for interval in intervals: station_duration = int(round(interval*60/stations.get(station).precipitation)) to_sprinkle[station] = self._update_schedule(to_sprinkle[station], self.modulo, week_min, week_min+station_duration) week_min += station_duration + int(round(station_duration*pause_ratio)) logging.debug('Weather based deficit for %s: %s', stations.get(station).name, str(sorted([((now.date() + datetime.timedelta(days=x)).isoformat(), y) for x, y in station_balance.iteritems()]))) self._station_schedule = to_sprinkle except Exception: logging.warning('Could not create weather based schedule:\n' + traceback.format_exc())
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 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 _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 run(self): log.clear(NAME) error_check = False # error signature relay_pins = [0] relay_count = -1 msg_debug_err = True msg_debug_on = [ True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True ] msg_debug_off = [ True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True ] time_cleaner = 0 if not plugin_options['enabled']: log.info(NAME, _('Relay 16 plug-in') + ': ' + _('is disabled.')) while not self._stop_event.is_set(): try: if relay_count != plugin_options['relays'] and plugin_options[ 'enabled']: relay_count = plugin_options['relays'] log.clear(NAME) log.info( NAME, _('Relay 16 plug-in') + ': ' + datetime_string() + ' ' + str(plugin_options['relays']) + ' ' + _('Outputs set.')) #### define the GPIO pins that will be used #### try: if determine_platform( ) == 'pi': # If this will run on Raspberry Pi: import RPi.GPIO as GPIO # RPi hardware GPIO.setmode( GPIO.BOARD ) #IO channels are identified by header connector pin numbers. Pin numbers are if get_rpi_revision() >= 2: relay_pins = [ 22, 24, 26, 32, 36, 38, 40, 21, 23, 29, 31, 33, 35, 37, 18, 19 ] ### associations for outputs HW connector PINs log.info( NAME, _('Relay 16 plug-in') + ': ' + _('Possible GPIO pins') + str(relay_pins) + '.') for i in range( plugin_options['relays'] ): # count 1 or 2, 4, 8, 16 outputs try: GPIO.setup( relay_pins[i], GPIO.OUT) # set pin as outputs except: error_check = True # not set pins -> this is error log.error( NAME, _('Relay 16 plug-in') + ':\n' + traceback.format_exc()) else: log.info( NAME, _('Relay 16 plug-in') + ': ' + _('Sorry Raspberry Pi 1 is old version.')) error_check = True else: log.info( NAME, _('Relay 16 plug-in') + ': ' + _('Relay board plugin only supported on Raspberry Pi.' )) except: if msg_debug_err: log.error( NAME, _('Relay 16 plug-in') + ':\n' + traceback.format_exc()) msg_debug_err = False error_check = False pass #### plugin if plugin_options['enabled']: # if plugin is enabled if error_check == False: # if not check error for station in stations.get(): if station.index + 1 <= plugin_options[ 'relays']: # only if station count is < count relay outputs ### ON if station.active: # stations is on if plugin_options[ 'active'] == 'high': # normal high logic # relay output on to 3.3V GPIO.output(relay_pins[station.index], GPIO.HIGH) if msg_debug_on[station.index]: log.info( NAME, _('Relay 16 plug-in') + ': ' + datetime_string() + ' ' + _('Setings Relay Output') + ' ' + str(relay_pins[station.index]) + ' ' + _('to HIGH') + ' (' + _('Station') + ' ' + str(station.index + 1) + ' ' + _('ON') + ').') msg_debug_on[station.index] = False msg_debug_off[station.index] = True else: # inversion low logic # relay output on to 0V GPIO.output(relay_pins[station.index], GPIO.LOW) if msg_debug_on[station.index]: log.info( NAME, _('Relay 16 plug-in') + ': ' + datetime_string() + ' ' + _('Setings Relay Output') + ' ' + str(relay_pins[station.index]) + ' ' + _('to LOW') + ' (' + _('Station') + ' ' + str(station.index + 1) + ' ' + _('ON') + ').') msg_debug_on[station.index] = False msg_debug_off[station.index] = True ### OFF else: # stations is off if plugin_options[ 'active'] == 'high': # normal high logic # relay output off to 0V GPIO.output(relay_pins[station.index], GPIO.LOW) if msg_debug_off[station.index]: log.info( NAME, _('Relay 16 plug-in') + ': ' + datetime_string() + ' ' + _('Setings Relay Output') + ' ' + str(relay_pins[station.index]) + ' ' + _('to LOW') + ' (' + _('Station') + ' ' + str(station.index + 1) + ' ' + _('OFF') + ').') msg_debug_off[ station.index] = False msg_debug_on[station.index] = True else: # inversion low logic # relay output off to 3.3V GPIO.output(relay_pins[station.index], GPIO.HIGH) if msg_debug_off[station.index]: log.info( NAME, _('Relay 16 plug-in') + ': ' + datetime_string() + ' ' + _('Setings Relay Output') + ' ' + str(relay_pins[station.index]) + ' ' + _('to HIGH') + ' (' + _('Station') + ' ' + str(station.index + 1) + ' ' + _('OFF') + ').') msg_debug_off[ station.index] = False msg_debug_on[station.index] = True time.sleep(0.5) time_cleaner += 1 if time_cleaner >= 120: # 60 sec timer (ex: 120 * time.sleep(0.5) is 60 sec) time_cleaner = 0 relay_count = -1 msg_debug_err = True msg_debug_on = [ True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True ] msg_debug_off = [ True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True ] except Exception: log.error( NAME, _('Relay 16 plug-in') + ':\n' + traceback.format_exc()) msg = -1 self._sleep(60)
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): log.clear(NAME) if not plugin_options['enabled']: log.info(NAME, _('Pressurizer is disabled.')) else: log.info(NAME, _('Pressurizer is enabled.')) start_master = False # for master station ON/OFF pressurizer_master_relay_off.send( ) # send signal relay off from this plugin while not self._stop_event.is_set(): try: if plugin_options['enabled']: # plugin is enabled current_time = datetime.datetime.now() user_pre_time = current_time + datetime.timedelta( seconds=int(plugin_options['pre_time'])) check_start = current_time - datetime.timedelta(days=1) check_end = current_time + datetime.timedelta(days=1) schedule = predicted_schedule(check_start, check_end) rain = not options.manual_mode and ( rain_blocks.block_end() > datetime.datetime.now() or inputs.rain_sensed()) if stations.master is None: start_master = False log.clear(NAME) log.info( NAME, datetime_string() + ' ' + _('This plugin requires setting master station to enabled. Setup this in options! And also enable the relay as master station in options!' )) self._sleep(10) for entry in schedule: if entry['start'] <= user_pre_time < entry[ 'end']: # is possible program in this interval? if not rain and not entry[ 'blocked']: # is not blocked and not ignored rain? if stations.master is not None: log.clear(NAME) log.info( NAME, datetime_string() + ' ' + _('Is time for pump running...')) start_master = True if start_master: # is time for run relay pname = _('Pressurizer plug-in') program_name = "%s " % pname.encode( "utf-8", errors="ignore").decode( "utf-8") # program name sname = 0 for station in stations.get(): if station.is_master: sname = station.index # master pump index pend = current_time + datetime.timedelta( seconds=int(plugin_options['run_time'])) _entry = { 'active': None, 'program': -1, 'program_name': program_name, 'fixed': True, 'cut_off': 0, 'manual': True, 'blocked': False, 'start': datetime.datetime.now(), 'original_start': datetime.datetime.now(), 'end': pend, 'uid': '%s-%s-%d' % (datetime.datetime.now(), str(program_name), sname), 'usage': 2.0, 'station': sname } if plugin_options['relay']: pressurizer_master_relay_on.send( ) # send signal relay on from this plugin self._sleep(0.5) outputs.relay_output = True # activate relay log.info(NAME, _('Activating relay.')) log.start_run(_entry) wait_for_run = plugin_options[ 'run_time'] # pump run time if wait_for_run > plugin_options[ 'pre_time']: # is not run time > pre run time? wait_for_run = plugin_options[ 'pre_time'] # scheduller tick is 1 second log.info( NAME, datetime_string() + ' ' + _('Waiting') + ' ' + str(wait_for_run) + ' ' + _('second.')) self._sleep( int(wait_for_run)) # waiting on run time pressurizer_master_relay_off.send( ) # send signal relay off from this plugin self._sleep(0.5) outputs.relay_output = False # deactivate relay log.info(NAME, _('Deactivating relay.')) log.finish_run(_entry) log.info(NAME, datetime_string() + ' ' + _('Ready.')) start_master = False seconds = int( plugin_options['mm'] or 0) * 60 + int( plugin_options['ss'] or 0) # (mm:ss) log.info( NAME, datetime_string() + ' ' + _('Waiting') + ' ' + str(seconds) + ' ' + _('second.')) self._sleep( seconds ) # How long after the relay is activated wait for another stations else: self._sleep(2) else: self._sleep(5) self._sleep(1) except Exception: log.error( NAME, _('Pressurizer plug-in') + ':\n' + traceback.format_exc()) self._sleep(60)
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