Ich habe min/max-Temperaturanzeigen hinzugefügt. Seitdem stoppt mein System jeweils zu Tagesbeginn (ca 5-6 Uhr), d.h. die 1-Wire-Funktionen sind tot.
KNX-Funktionen laufen noch, Visu geht.
Mein Verdacht liegt irgendwo bei der daily-Funktion. Wer kann mir eine Tipp geben.
Da leider die Anzeige noch keine 28-Stunden überlebt hat, habe ich noch eine Zusatzfrage. 3 Räume werden abgefragt. Einmal sind min/max-Werte versorgt, 2 x ist min = 0 , max richtig.
Ist das ein Timingproblem? Erster Wert nach dem Start (unversorgt) = 0, danach wirds nicht mehr kälter, max bei der nächsten Abfrage?
Aber hier noch Nebenschauplatz. Erst einmal ist der Absturz der 1-Wirefunktion zu klären.
Anbei alle codings aus der Fehlermeldung!
Danke vorab für jegliche Hilfe"
Wolfgang
Logfile:
Code:
2015-07-16 03:04:05 INFO wire.temp_r2.min Item wire.temp_r2.min: evaluating sh.wire.temp_r2.db('min', '28h') returns None
2015-07-16 03:04:05 INFO wire.temp_r2.max Item wire.temp_r2.max: evaluating sh.wire.temp_r2.db('max', '28h') returns None
2015-07-16 03:04:11 INFO wire.temp_r1.max Item wire.temp_r1.max: evaluating sh.wire.temp_r1.db('max', '28h') returns None
2015-07-16 03:04:11 INFO wire.temp_r1.min Item wire.temp_r1.min: evaluating sh.wire.temp_r1.db('min', '28h') returns None
2015-07-16 06:00:00 ERROR env_daily Logic: env_daily, File: /usr/local/smarthome/lib/scheduler.py, Line: 285, Method: _next_time, Exception: list indices must be integers, not str
Traceback (most recent call last):
File "/usr/local/smarthome/lib/scheduler.py", line 327, in _task
exec(obj.bytecode)
File "/usr/local/smarthome/lib/env/daily.py", line 7, in <module>
sh.scheduler.change(logic.name, cron="{} {} * *".format(m, h))
File "/usr/local/smarthome/lib/scheduler.py", line 255, in change
self._next_time(name)
File "/usr/local/smarthome/lib/scheduler.py", line 285, in _next_time
value = job['cron'][entry]
TypeError: list indices must be integers, not str
Code:
[wire)
[[temp_r1]]
# name = Temp-Vorratskeller
type = num
visu_acl = rw
knx_dpt = 9
ow_addr = 26.59CD89010000
ow_sensor = T
knx_cache = 4/3/0
knx_send = 4/3/0
visu_acl = rw
eval=round(value,2)
sqlite = init
sqlite = yes
[[['min']]]
type = num
knx_dpt = 9
knx_send = 4/3/30
knx_reply = 4/3/30
eval = "sh.wire.temp_r1.db('min', '28h')"
eval_trigger = wire.temp_r1
[[['max']]]
type = num
knx_dpt = 9
knx_send = 4/3/31
knx_reply = 4/3/31
eval = "sh.wire.temp_r1.db('max', '28h')"
eval_trigger = wire.temp_r1
Crontab wird sonst nur nach hier genutzt:
Code:
[system]
[[datum_uhrzeit]]
[[[sonne]]]
[[[[berechnung]]]]
type = bool
visu = yes
enforce_updates = true
crontab = init | 1 0 * *
[[[[aufgang]]]]
type = foo
visu = yes
eval = sh.sun.rise().astimezone(sh.tzinfo()).strftime("%H:%M")
eval_trigger = system.datum_uhrzeit.sonne.berechnung()
enforce_updates = true
[[[[untergang]]]]
type = foo
visu = yes
eval = sh.sun.set().astimezone(sh.tzinfo()).strftime("%H:%M")
eval_trigger = system.datum_uhrzeit.sonne.berechnung()
enforce_updates = true
[[[[position]]]]
type = foo
visu = yes
eval = sh.sun.pos()
eval_trigger = system.datum_uhrzeit.sonne.berechnung()
enforce_updates = true
[[[mond]]]
[[[[berechnung]]]]
type = bool
visu = yes
eval_trigger = system.datum_uhrzeit.mond.berechnung()
enforce_updates = true
crontab = init | 1 0 * *
[[[[aufgang]]]]
type = foo
sheduler.py
Code:
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011-2013 Marcus Popp marcus@popp.mx
#########################################################################
# This file is part of SmartHome.py. http://mknx.github.io/smarthome/
#
# SmartHome.py is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SmartHome.py is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SmartHome.py. If not, see <http://www.gnu.org/licenses/>.
##########################################################################
import gc # noqa
import logging
import time
import datetime
import calendar
import sys
import traceback
import threading
import os # noqa
import random
import types # noqa
import subprocess # noqa
import dateutil.relativedelta
from dateutil.relativedelta import MO, TU, WE, TH, FR, SA, SU
from dateutil.tz import tzutc
logger = logging.getLogger('')
class PriorityQueue:
def __init__(self):
self.queue = []
self.lock = threading.Lock()
def insert(self, priority, data):
self.lock.acquire()
lo = 0
hi = len(self.queue)
while lo < hi:
mid = (lo + hi) // 2
if priority < self.queue[mid][0]:
hi = mid
else:
lo = mid + 1
self.queue.insert(lo, (priority, data))
self.lock.release()
def get(self):
self.lock.acquire()
try:
return self.queue.pop(0)
except IndexError:
raise
finally:
self.lock.release()
def qsize(self):
return len(self.queue)
class Scheduler(threading.Thread):
_workers = []
_worker_num = 5
_worker_max = 20
_worker_delta = 60 # wait 60 seconds before adding another worker thread
_scheduler = {}
_runq = PriorityQueue()
_triggerq = PriorityQueue()
def __init__(self, smarthome):
threading.Thread.__init__(self, name='Scheduler')
logger.info('Init Scheduler')
self._sh = smarthome
self._lock = threading.Lock()
self._runc = threading.Condition()
def run(self):
self.alive = True
logger.debug("creating {0} workers".format(self._worker_num))
for i in range(self._worker_num):
self._add_worker()
while self.alive:
now = self._sh.now()
if self._runq.qsize() > len(self._workers):
delta = now - self._last_worker
if delta.seconds > self._worker_delta:
if len(self._workers) < self._worker_max:
self._add_worker()
else:
logger.error("Needing more worker threads than the specified maximum of {0}!".format(self._worker_max))
tn = {}
for t in threading.enumerate():
tn[t.name] = tn.get(t.name, 0) + 1
logger.info('Threads: ' + ', '.join("{0}: {1}".format(k, v) for (k, v) in list(tn.items())))
self._add_worker()
while self._triggerq.qsize() > 0:
try:
(dt, prio), (name, obj, by, source, dest, value) = self._triggerq.get()
except Exception as e:
logger.warning("Trigger queue exception: {0}".format(e))
break
if dt < now: # run it
self._runc.acquire()
self._runq.insert(prio, (name, obj, by, source, dest, value))
self._runc.notify()
self._runc.release()
else: # put last entry back and break while loop
self._triggerq.insert((dt, prio), (name, obj, by, source, dest, value))
break
self._lock.acquire()
for name in self._scheduler:
task = self._scheduler[name]
if task['next'] is not None:
if task['next'] < now:
self._runc.acquire()
self._runq.insert(task['prio'], (name, task['obj'], 'Scheduler', None, None, task['value']))
self._runc.notify()
self._runc.release()
task['next'] = None
else:
continue
elif not task['active']:
continue
else:
if task['cron'] is None and task['cycle'] is None:
continue
else:
self._next_time(name)
self._lock.release()
time.sleep(0.5)
def stop(self):
self.alive = False
def trigger(self, name, obj=None, by='Logic', source=None, value=None, dest=None, prio=3, dt=None):
if obj is None:
if name in self._scheduler:
obj = self._scheduler[name]['obj']
else:
logger.warning("Logic name not found: {0}".format(name))
return
if name in self._scheduler:
if not self._scheduler[name]['active']:
logger.debug("Logic '{0}' deactivated. Ignoring trigger from {1} {2}".format(name, by, source))
return
if dt is None:
logger.debug("Triggering {0} - by: {1} source: {2} dest: {3} value: {4}".format(name, by, source, dest, str(value)[:40]))
self._runc.acquire()
self._runq.insert(prio, (name, obj, by, source, dest, value))
self._runc.notify()
self._runc.release()
else:
if not isinstance(dt, datetime.datetime):
logger.warning("Trigger: Not a valid timezone aware datetime for {0}. Ignoring.".format(name))
return
if dt.tzinfo is None:
logger.warning("Trigger: Not a valid timezone aware datetime for {0}. Ignoring.".format(name))
return
logger.debug("Triggering {0} - by: {1} source: {2} dest: {3} value: {4} at: {5}".format(name, by, source, dest, str(value)[:40], dt))
self._triggerq.insert((dt, prio), (name, obj, by, source, dest, value))
def remove(self, name):
self._lock.acquire()
if name in self._scheduler:
del(self._scheduler[name])
self._lock.release()
def return_next(self, name):
if name in self._scheduler:
return self._scheduler[name]['next']
def add(self, name, obj, prio=3, cron=None, cycle=None, value=None, offset=None, next=None):
self._lock.acquire()
if isinstance(cron, str):
cron = [cron, ]
if isinstance(cron, list):
_cron = {}
for entry in cron:
desc, __, _value = entry.partition('=')
desc = desc.strip()
if _value == '':
_value = None
else:
_value = _value.strip()
if desc.startswith('init'):
offset = 5 # default init offset
desc, op, seconds = desc.partition('+')
if op:
offset += int(seconds)
else:
desc, op, seconds = desc.partition('-')
if op:
offset -= int(seconds)
value = _value
next = self._sh.now() + datetime.timedelta(seconds=offset)
else:
_cron[desc] = _value
if _cron == {}:
cron = None
else:
cron = _cron
if isinstance(cycle, int):
cycle = {cycle: None}
elif isinstance(cycle, str):
cycle, __, _value = cycle.partition('=')
try:
cycle = int(cycle.strip())
except Exception:
logger.warning("Scheduler: invalid cycle entry for {0} {1}".format(name, cycle))
return
if _value != '':
_value = _value.strip()
else:
_value = None
cycle = {cycle: _value}
if cycle is not None and offset is None: # spread cycle jobs
offset = random.randint(10, 15)
self._scheduler[name] = {'prio': prio, 'obj': obj, 'cron': cron, 'cycle': cycle, 'value': value, 'next': next, 'active': True}
if next is None:
self._next_time(name, offset)
self._lock.release()
def change(self, name, **kwargs):
if name in self._scheduler:
for key in kwargs:
if key in self._scheduler[name]:
if key == 'cron':
if isinstance(kwargs[key], str):
kwargs[key] = kwargs[key].split('|')
elif key == 'active':
if kwargs['active'] and not self._scheduler[name]['active']:
logger.info("Activating logic: {0}".format(name))
elif not kwargs['active'] and self._scheduler[name]['active']:
logger.info("Deactivating logic: {0}".format(name))
self._scheduler[name][key] = kwargs[key]
else:
logger.warning("Attribute {0} for {1} not specified. Could not change it.".format(key, name))
if self._scheduler[name]['active'] is True:
if 'cycle' in kwargs or 'cron' in kwargs:
self._next_time(name)
else:
self._scheduler[name]['next'] = None
else:
logger.warning("Could not change {0}. No logic/method with this name found.".format(name))
def _next_time(self, name, offset=None):
job = self._scheduler[name]
if None == job['cron'] == job['cycle']:
self._scheduler[name]['next'] = None
return
next_time = None
value = None
now = self._sh.now()
now = now.replace(microsecond=0)
if job['cycle'] is not None:
cycle = list(job['cycle'].keys())[0]
value = job['cycle'][cycle]
if offset is None:
offset = cycle
next_time = now + datetime.timedelta(seconds=offset)
if job['cron'] is not None:
for entry in job['cron']:
ct = self._crontab(entry)
if next_time is not None:
if ct < next_time:
next_time = ct
value = job['cron'][entry]
else:
next_time = ct
value = job['cron'][entry]
self._scheduler[name]['next'] = next_time
self._scheduler[name]['value'] = value
if name not in ['Connections', 'series', 'SQLite dump']:
logger.debug("{0} next time: {1}".format(name, next_time))
def __iter__(self):
for job in self._scheduler:
yield job
def _add_worker(self):
self._last_worker = self._sh.now()
t = threading.Thread(target=self._worker)
t.start()
self._workers.append(t)
if len(self._workers) > self._worker_num:
logger.info("Adding worker thread. Total: {0}".format(len(self._workers)))
tn = {}
for t in threading.enumerate():
tn[t.name] = tn.get(t.name, 0) + 1
logger.info('Threads: ' + ', '.join("{0}: {1}".format(k, v) for (k, v) in list(tn.items())))
def _worker(self):
while self.alive:
self._runc.acquire()
self._runc.wait(timeout=1)
try:
prio, (name, obj, by, source, dest, value) = self._runq.get()
except IndexError:
continue
finally:
self._runc.release()
self._task(name, obj, by, source, dest, value)
def _task(self, name, obj, by, source, dest, value):
threading.current_thread().name = name
logger = logging.getLogger(name)
if obj.__class__.__name__ == 'Logic':
trigger = {'by': by, 'source': source, 'dest': dest, 'value': value} # noqa
logic = obj # noqa
sh = self._sh # noqa
try:
exec(obj.bytecode)
except SystemExit:
# ignore exit() call from logic.
pass
except Exception as e:
tb = sys.exc_info()[2]
tb = traceback.extract_tb(tb)[-1]
logger.exception("Logic: {0}, File: {1}, Line: {2}, Method: {3}, Exception: {4}".format(name, tb[0], tb[1], tb[2], e))
elif obj.__class__.__name__ == 'Item':
try:
if value is not None:
obj(value, caller="Scheduler")
except Exception as e:
logger.exception("Item {0} exception: {1}".format(name, e))
else: # method
try:
if value is None:
obj()
else:
obj(**value)
except Exception as e:
logger.exception("Method {0} exception: {1}".format(name, e))
threading.current_thread().name = 'idle'
def _crontab(self, crontab):
try:
# process sunrise/sunset
for entry in crontab.split('<'):
if entry.startswith('sun'):
return self._sun(crontab)
next_event = self._parse_month(crontab) # this month
if not next_event:
next_event = self._parse_month(crontab, next_month=True) # next month
return next_event
except:
logger.error("Error parsing crontab: {}".format(crontab))
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
def _parse_month(self, crontab, next_month=False):
now = self._sh.now()
minute, hour, day, wday = crontab.split(' ')
# evaluate the crontab strings
minute_range = self._range(minute, 00, 59)
hour_range = self._range(hour, 00, 23)
if not next_month:
mdays = calendar.monthrange(now.year, now.month)[1]
elif now.month == 12:
mdays = calendar.monthrange(now.year + 1, 1)[1]
else:
mdays = calendar.monthrange(now.year, now.month + 1)[1]
if wday == '*' and day == '*':
day_range = self._day_range('0, 1, 2, 3, 4, 5, 6')
elif wday != '*' and day == '*':
day_range = self._day_range(wday)
elif wday != '*' and day != '*':
day_range = self._day_range(wday)
day_range = day_range + self._range(day, 0o1, mdays)
else:
day_range = self._range(day, 0o1, mdays)
# combine the differnt ranges
event_range = sorted([str(day) + '-' + str(hour) + '-' + str(minute) for minute in minute_range for hour in hour_range for day in day_range])
if next_month: # next month
next_event = event_range[0]
next_time = now + dateutil.relativedelta.relativedelta(months=+1)
else: # this month
now_str = now.strftime("%d-%H-%M")
next_event = self._next(lambda event: event > now_str, event_range)
if not next_event:
return False
next_time = now
day, hour, minute = next_event.split('-')
return next_time.replace(day=int(day), hour=int(hour), minute=int(minute), second=0, microsecond=0)
def _next(self, f, seq):
for item in seq:
if f(item):
return item
return False
def _sun(self, crontab):
if not self._sh.sun: # no sun object created
logger.warning('No latitude/longitude specified. You could not use sunrise/sunset as crontab entry.')
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
# find min/max times
tabs = crontab.split('<')
if len(tabs) == 1:
smin = None
cron = tabs[0].strip()
smax = None
elif len(tabs) == 2:
if tabs[0].startswith('sun'):
smin = None
cron = tabs[0].strip()
smax = tabs[1].strip()
else:
smin = tabs[0].strip()
cron = tabs[1].strip()
smax = None
elif len(tabs) == 3:
smin = tabs[0].strip()
cron = tabs[1].strip()
smax = tabs[2].strip()
else:
logger.error('Wrong syntax: {0}. Should be [H:M<](sunrise|sunset)[+|-][offset][<H:M]'.format(crontab))
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
doff = 0 # degree offset
moff = 0 # minute offset
tmp, op, offs = cron.rpartition('+')
if op:
if offs.endswith('m'):
moff = int(offs.strip('m'))
else:
doff = float(offs)
else:
tmp, op, offs = cron.rpartition('-')
if op:
if offs.endswith('m'):
moff = -int(offs.strip('m'))
else:
doff = -float(offs)
if cron.startswith('sunrise'):
next_time = self._sh.sun.rise(doff, moff)
elif cron.startswith('sunset'):
next_time = self._sh.sun.set(doff, moff)
else:
logger.error('Wrong syntax: {0}. Should be [H:M<](sunrise|sunset)[+|-][offset][<H:M]'.format(crontab))
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
if smin is not None:
h, sep, m = smin.partition(':')
try:
dmin = next_time.replace(hour=int(h), minute=int(m), second=0, tzinfo=self._sh.tzinfo())
except Exception:
logger.error('Wrong syntax: {0}. Should be [H:M<](sunrise|sunset)[+|-][offset][<H:M]'.format(crontab))
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
if dmin > next_time:
next_time = dmin
if smax is not None:
h, sep, m = smax.partition(':')
try:
dmax = next_time.replace(hour=int(h), minute=int(m), second=0, tzinfo=self._sh.tzinfo())
except Exception:
logger.error('Wrong syntax: {0}. Should be [H:M<](sunrise|sunset)[+|-][offset][<H:M]'.format(crontab))
return datetime.datetime.now(tzutc()) + dateutil.relativedelta.relativedelta(years=+10)
if dmax < next_time:
if dmax < self._sh.now():
dmax = dmax + datetime.timedelta(days=1)
next_time = dmax
return next_time
def _range(self, entry, low, high):
result = []
item_range = []
if entry == '*':
item_range = list(range(low, high + 1))
else:
for item in entry.split(','):
item = int(item)
if item > high: # entry above range
item = high # truncate value to highest possible
item_range.append(item)
for entry in item_range:
result.append('{:02d}'.format(entry))
return result
def _day_range(self, days):
now = datetime.date.today()
wdays = [MO, TU, WE, TH, FR, SA, SU]
result = []
for day in days.split(','):
wday = wdays[int(day)]
# add next weekday occurence
day = now + dateutil.relativedelta.relativedelta(weekday=wday)
result.append(day.strftime("%d"))
# safety add-on if weekday equals todays weekday
day = now + dateutil.relativedelta.relativedelta(weekday=wday(+2))
result.append(day.strftime("%d"))
return result
Code:
#!/usr/bin/env python3
#
if trigger['value'] == 'init':
h = random.randrange(2, 5)
m = random.randrange(0, 59)
sh.scheduler.change(logic.name, cron="{} {} * *".format(m, h))
exit()
import hashlib
import urllib.parse
import lib.www
## check version
data = {}
try:
data['up'] = int((sh.now() - sh.env.core.start()).total_seconds() / 86400)
data['it'] = sh.item_count
data['pl'] = len(sh._plugins._plugins)
__ = sh.version.split('.')
lmajor = int(__[0])
lminor = int(__[1])
lchange = int(__[2])
data['ma'] = lmajor
data['mi'] = lminor
data['ch'] = lchange
if len(__) == 4:
data['br'] = __[3]
except:
pass
try:
with open('/sys/class/net/eth0/address', 'r') as f:
__ = f.readline().strip()
data['mc'] = hashlib.md5(__.encode()).hexdigest()
except:
pass
#try:
# body = urllib.parse.urlencode(data)
# content = lib.www.Client().fetch_url("http://get.smarthomepy.de/version", method='POST', body=body, timeout=5)
# if content:
# rmajor, rminor, rchange = content.decode().split('.')
# if int(rmajor) > lmajor or (int(rmajor) == lmajor and int(rminor) > lminor):
# sh.env.core.upgrade(True)
# elif int(rchange) > lchange:
# sh.env.core.update(True)
#except:
# pass


Kommentar