887 changes: 887 additions & 0 deletions lib/3rd/dateutil/parser.py

Large diffs are not rendered by default.

431 changes: 431 additions & 0 deletions lib/3rd/dateutil/relativedelta.py

Large diffs are not rendered by default.

1,103 changes: 1,103 additions & 0 deletions lib/3rd/dateutil/rrule.py

Large diffs are not rendered by default.

940 changes: 940 additions & 0 deletions lib/3rd/dateutil/tz.py

Large diffs are not rendered by default.

180 changes: 180 additions & 0 deletions lib/3rd/dateutil/tzwin.py
@@ -0,0 +1,180 @@
# This code was originally contributed by Jeffrey Harris.
import datetime
import struct
import winreg

__author__ = "Jeffrey Harris & Gustavo Niemeyer <gustavo@niemeyer.net>"

__all__ = ["tzwin", "tzwinlocal"]

ONEWEEK = datetime.timedelta(7)

TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones"
TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones"
TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"

def _settzkeyname():
global TZKEYNAME
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
try:
winreg.OpenKey(handle, TZKEYNAMENT).Close()
TZKEYNAME = TZKEYNAMENT
except WindowsError:
TZKEYNAME = TZKEYNAME9X
handle.Close()

_settzkeyname()

class tzwinbase(datetime.tzinfo):
"""tzinfo class based on win32's timezones available in the registry."""

def utcoffset(self, dt):
if self._isdst(dt):
return datetime.timedelta(minutes=self._dstoffset)
else:
return datetime.timedelta(minutes=self._stdoffset)

def dst(self, dt):
if self._isdst(dt):
minutes = self._dstoffset - self._stdoffset
return datetime.timedelta(minutes=minutes)
else:
return datetime.timedelta(0)

def tzname(self, dt):
if self._isdst(dt):
return self._dstname
else:
return self._stdname

def list():
"""Return a list of all time zones known to the system."""
handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
tzkey = winreg.OpenKey(handle, TZKEYNAME)
result = [winreg.EnumKey(tzkey, i)
for i in range(winreg.QueryInfoKey(tzkey)[0])]
tzkey.Close()
handle.Close()
return result
list = staticmethod(list)

def display(self):
return self._display

def _isdst(self, dt):
dston = picknthweekday(dt.year, self._dstmonth, self._dstdayofweek,
self._dsthour, self._dstminute,
self._dstweeknumber)
dstoff = picknthweekday(dt.year, self._stdmonth, self._stddayofweek,
self._stdhour, self._stdminute,
self._stdweeknumber)
if dston < dstoff:
return dston <= dt.replace(tzinfo=None) < dstoff
else:
return not dstoff <= dt.replace(tzinfo=None) < dston


class tzwin(tzwinbase):

def __init__(self, name):
self._name = name

handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)
tzkey = winreg.OpenKey(handle, "%s\%s" % (TZKEYNAME, name))
keydict = valuestodict(tzkey)
tzkey.Close()
handle.Close()

self._stdname = keydict["Std"].encode("iso-8859-1")
self._dstname = keydict["Dlt"].encode("iso-8859-1")

self._display = keydict["Display"]

# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
tup = struct.unpack("=3l16h", keydict["TZI"])
self._stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1
self._dstoffset = self._stdoffset-tup[2] # + DaylightBias * -1

(self._stdmonth,
self._stddayofweek, # Sunday = 0
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[4:9]

(self._dstmonth,
self._dstdayofweek, # Sunday = 0
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[12:17]

def __repr__(self):
return "tzwin(%s)" % repr(self._name)

def __reduce__(self):
return (self.__class__, (self._name,))


class tzwinlocal(tzwinbase):

def __init__(self):

handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE)

tzlocalkey = winreg.OpenKey(handle, TZLOCALKEYNAME)
keydict = valuestodict(tzlocalkey)
tzlocalkey.Close()

self._stdname = keydict["StandardName"].encode("iso-8859-1")
self._dstname = keydict["DaylightName"].encode("iso-8859-1")

try:
tzkey = winreg.OpenKey(handle, "%s\%s"%(TZKEYNAME, self._stdname))
_keydict = valuestodict(tzkey)
self._display = _keydict["Display"]
tzkey.Close()
except OSError:
self._display = None

handle.Close()

self._stdoffset = -keydict["Bias"]-keydict["StandardBias"]
self._dstoffset = self._stdoffset-keydict["DaylightBias"]


# See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm
tup = struct.unpack("=8h", keydict["StandardStart"])

(self._stdmonth,
self._stddayofweek, # Sunday = 0
self._stdweeknumber, # Last = 5
self._stdhour,
self._stdminute) = tup[1:6]

tup = struct.unpack("=8h", keydict["DaylightStart"])

(self._dstmonth,
self._dstdayofweek, # Sunday = 0
self._dstweeknumber, # Last = 5
self._dsthour,
self._dstminute) = tup[1:6]

def __reduce__(self):
return (self.__class__, ())

def picknthweekday(year, month, dayofweek, hour, minute, whichweek):
"""dayofweek == 0 means Sunday, whichweek 5 means last instance"""
first = datetime.datetime(year, month, 1, hour, minute)
weekdayone = first.replace(day=((dayofweek-first.isoweekday())%7+1))
for n in range(whichweek):
dt = weekdayone+(whichweek-n)*ONEWEEK
if dt.month == month:
return dt

def valuestodict(key):
"""Convert a registry key's values to a dictionary."""
dict = {}
size = winreg.QueryInfoKey(key)[1]
for i in range(size):
data = winreg.EnumValue(key, i)
dict[data[0]] = data[1]
return dict
86 changes: 86 additions & 0 deletions lib/3rd/dateutil/zoneinfo/__init__.py
@@ -0,0 +1,86 @@
"""
Copyright (c) 2003-2005 Gustavo Niemeyer <gustavo@niemeyer.net>
This module offers extensions to the standard python 2.3+
datetime module.
"""
from dateutil.tz import tzfile
from tarfile import TarFile
import os

__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
__license__ = "PSF License"

__all__ = ["setcachesize", "gettz", "rebuild"]

CACHE = []
CACHESIZE = 10

class tzfile(tzfile):
def __reduce__(self):
return (gettz, (self._filename,))

def getzoneinfofile():
filenames = sorted(os.listdir(os.path.join(os.path.dirname(__file__))))
filenames.reverse()
for entry in filenames:
if entry.startswith("zoneinfo") and ".tar." in entry:
return os.path.join(os.path.dirname(__file__), entry)
return None

ZONEINFOFILE = getzoneinfofile()

del getzoneinfofile

def setcachesize(size):
global CACHESIZE, CACHE
CACHESIZE = size
del CACHE[size:]

def gettz(name):
tzinfo = None
if ZONEINFOFILE:
for cachedname, tzinfo in CACHE:
if cachedname == name:
break
else:
tf = TarFile.open(ZONEINFOFILE)
try:
zonefile = tf.extractfile(name)
except KeyError:
tzinfo = None
else:
tzinfo = tzfile(zonefile)
tf.close()
CACHE.insert(0, (name, tzinfo))
del CACHE[CACHESIZE:]
return tzinfo

def rebuild(filename, tag=None, format="gz"):
import tempfile, shutil
tmpdir = tempfile.mkdtemp()
zonedir = os.path.join(tmpdir, "zoneinfo")
moduledir = os.path.dirname(__file__)
if tag: tag = "-"+tag
targetname = "zoneinfo%s.tar.%s" % (tag, format)
try:
tf = TarFile.open(filename)
for name in tf.getnames():
if not (name.endswith(".sh") or
name.endswith(".tab") or
name == "leapseconds"):
tf.extract(name, tmpdir)
filepath = os.path.join(tmpdir, name)
os.system("zic -d %s %s" % (zonedir, filepath))
tf.close()
target = os.path.join(moduledir, targetname)
for entry in os.listdir(moduledir):
if entry.startswith("zoneinfo") and ".tar." in entry:
os.unlink(os.path.join(moduledir, entry))
tf = TarFile.open(target, "w:%s" % format)
for entry in os.listdir(zonedir):
entrypath = os.path.join(zonedir, entry)
tf.add(entrypath, entry)
tf.close()
finally:
shutil.rmtree(tmpdir)
Binary file added lib/3rd/dateutil/zoneinfo/zoneinfo-2011d.tar.gz
Binary file not shown.
165 changes: 165 additions & 0 deletions lib/3rd/rrdtool/LICENSE
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007

Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.


This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

0. Additional Definitions.

As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

1. Exception to Section 3 of the GNU GPL.

You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

2. Conveying Modified Versions.

If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or

b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.

3. Object Code Incorporating Material from Library Header Files.

The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.

b) Accompany the object code with a copy of the GNU GPL and this license
document.

4. Combined Works.

You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.

b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.

c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.

d) Do one of the following:

0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.

1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.

e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)

5. Combined Libraries.

You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.

b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.

6. Revised Versions of the GNU Lesser General Public License.

The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
825 changes: 825 additions & 0 deletions lib/3rd/rrdtool/rrdtool.c

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions lib/3rd/rrdtool/setup.py
@@ -0,0 +1,23 @@
#!/usr/bin/env python3

from distutils.core import setup, Extension


def main():
module = Extension('rrdtool', sources=['rrdtool.c'],
libraries=['rrd'])

kwargs = dict(
name='python3-rrdtool',
version='0.1.1',
description='rrdtool bindings for Python 3',
keywords=['rrdtool'],
author='Marcus Popp, Christian Jurk, Hye-Shik Chang',
author_email='marcus@popp.mx',
ext_modules=[module]
)

setup(**kwargs)

if __name__ == '__main__':
main()
103 changes: 103 additions & 0 deletions lib/config.py
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2013 <AUTHOR> <EMAIL>
#########################################################################
# This file is part of SmartHome.py. http://smarthome.sourceforge.net/
#
# 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 logging

logger = logging.getLogger('')


def parse(filename, config=None):
valid_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'
valid_set = set(valid_chars)
if config is None:
config = {}
item = config
with open(filename, 'r') as f:
linenu = 0
parent = {}
for raw in f.readlines():
linenu += 1
line = raw.partition('#')[0].strip()
if line is '':
continue
if line[0] == '[': # item
brackets = 0
level = 0
closing = False
for index in range(len(line)):
if line[index] == '[' and not closing:
brackets += 1
level += 1
elif line[index] == ']':
closing = True
brackets -= 1
else:
closing = True
if line[index] not in valid_chars + "'":
logger.error("Problem parsing '{}' invalid character in line {}: {}. Valid characters are: {}".format(filename, linenu, line, valid_chars))
return config
if brackets != 0:
logger.error("Problem parsing '{}' unbalanced brackets in line {}: {}".format(filename, linenu, line))
return config
name = line.strip("[]'")
if level == 1:
if name not in config:
config[name] = {}
item = config[name]
parents = {}
parents[level] = item
else:
if level - 1 not in parents:
logger.error("Problem parsing '{}' no parent item defined for item in line {}: {}".format(filename, linenu, line))
return config
parent = parents[level - 1]
if name not in parent:
parent[name] = {}
item = parent[name]
parents[level] = item

else: # attribute
attr, sep, value = line.partition('=')
if sep is '':
continue
attr = attr.strip()
if not set(attr).issubset(valid_set):
logger.error("Problem parsing '{}' invalid character in line {}: {}. Valid characters are: {}".format(filename, linenu, attr, valid_chars))
continue
value = value.strip()
if value[0] == '"': # split values with "
values = list(map(str.strip, value.split('"')))
if ',' in values:
value = [x for x in values if x not in ['', ',']]
else:
value = value.strip('"')
elif ',' in value:
value = list(map(str.strip, value.split(',')))
else:
value = value.strip("'")
item[attr] = value
return config


if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
conf = parse('dev.conf')
print(conf)
77 changes: 43 additions & 34 deletions lib/item.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp marcus@popp.mx
Expand All @@ -22,7 +22,7 @@
import logging
import threading
import datetime
import cPickle
import pickle

logger = logging.getLogger('')

Expand Down Expand Up @@ -66,27 +66,25 @@ def __init__(self, smarthome, parent, path, config):
def parse(self, smarthome, parent, path, config):
# parse config
for attr in config:
if isinstance(config[attr], dict): # sub item
sub_path = self._path + '.' + attr
sub_item = smarthome.return_item(sub_path)
if sub_item is None: # new item
sub_item = Item(smarthome, self, sub_path, config[attr])
vars(self)[attr] = sub_item
smarthome.add_item(sub_path, sub_item)
self._sub_items.append(sub_item)
else: # existing item
sub_item.parse(smarthome, self, sub_path, config[attr])
else: # attribute
if not isinstance(config[attr], dict): # attribute
if attr == 'type':
self._type = config[attr]
elif attr == 'value':
self._value = config[attr]
elif attr == 'name':
self._name = config[attr]
elif attr == 'cache':
self._cache = self._return_bool(config[attr])
try:
self._cache = self._return_bool(config[attr])
except:
logger.error("Item '{0}' problem parsing '{1}'.Ignoring Item!".format(path, config[attr]))
return
elif attr == 'enforce_updates':
self._enforce_updates = self._return_bool(config[attr])
try:
self._enforce_updates = self._return_bool(config[attr])
except:
logger.error("Item '{0}' problem parsing '{1}'.Ignoring Item!".format(path, config[attr]))
return
elif attr == 'threshold':
self._threshold = config[attr]
elif attr == 'eval':
Expand All @@ -112,17 +110,28 @@ def parse(self, smarthome, parent, path, config):
self._autotimer = time, value
else:
self.conf[attr] = config[attr]
for attr in config:
if isinstance(config[attr], dict): # sub item
sub_path = self._path + '.' + attr
sub_item = smarthome.return_item(sub_path)
if sub_item is None: # new item
sub_item = Item(smarthome, self, sub_path, config[attr])
vars(self)[attr] = sub_item
smarthome.add_item(sub_path, sub_item)
self._sub_items.append(sub_item)
else: # existing item
sub_item.parse(smarthome, self, sub_path, config[attr])
if self._type is not None:
if self._type not in self._defaults:
logger.warning(u"Item {0}: type '{1}' unknown. Please use one of: {2}.".format(path, self._type, ', '.join(self._defaults.keys())))
logger.warning("Item {0}: type '{1}' unknown. Please use one of: {2}.".format(path, self._type, ', '.join(list(self._defaults.keys()))))
return
if self._value is None:
self._value = self._defaults[self._type]
else:
try:
self._value = getattr(self, '_return_' + self._type)(self._value)
except:
logger.error(u"Item '{0}': value ({1}) does not match type ({2}). Ignoring!".format(path, self._value, self._type))
logger.error("Item '{0}': value ({1}) does not match type ({2}). Ignoring!".format(path, self._value, self._type))
return
else:
#logger.debug("Item '{0}': No type specified.".format(self._path))
Expand Down Expand Up @@ -157,7 +166,7 @@ def init_prerun(self):
if item != self: # prevent loop
item._items_to_trigger.append(self)
if self._eval:
items = map(lambda x: 'sh.' + x.id() + '()', triggers)
items = ['sh.' + x.id() + '()' for x in triggers]
if self._eval == 'and':
self._eval = ' and '.join(items)
elif self._eval == 'or':
Expand Down Expand Up @@ -191,8 +200,8 @@ def _run_eval(self, value=None, caller='Eval', source=None, dest=None):
sh = self._sh # noqa
try:
value = eval(self._eval)
except Exception, e:
logger.warning("Problem with eval {0}: {1}".format(self._eval, e))
except Exception as e:
logger.warning("Problem evaluating {0}: {1}".format(self._eval, e))
else:
self._update(value, caller, source)

Expand All @@ -204,12 +213,12 @@ def set(self, value, caller='Logic', source=None, dest=None):
try:
value = getattr(self, '_return_' + self._type)(value)
except:
logger.error(u"Item '{0}': value ({1}) does not match type ({2}). Via {3} {4} => {5}".format(self._path, value, self._type, caller, source, dest))
logger.error("Item '{0}': value ({1}) does not match type ({2}). Via {3} {4} => {5}".format(self._path, value, self._type, caller, source, dest))
return
self._lock.acquire()
self._value = value
self._lock.release()
self._change_logger(u"{0} = {1} via {2} {3}".format(self._path, value, caller, source))
self._change_logger("{0} = {1} via {2} {3}".format(self._path, value, caller, source))

def __call__(self, value=None, caller='Logic', source=None, dest=None):
if value is None or self._type is None:
Expand All @@ -218,7 +227,7 @@ def __call__(self, value=None, caller='Logic', source=None, dest=None):
value = getattr(self, '_return_' + self._type)(value)
except:
try:
logger.error(u"Item '{0}': value ({1}) does not match type ({2}). Via {3} {4}".format(self._path, value, self._type, caller, source))
logger.error("Item '{0}': value ({1}) does not match type ({2}). Via {3} {4}".format(self._path, value, self._type, caller, source))
except:
pass
return
Expand All @@ -232,15 +241,15 @@ def _update(self, value=None, caller='Logic', source=None, dest=None):
try:
value = getattr(self, '_return_' + self._type)(value)
except:
logger.error(u"Item '{0}': value ({1}) does not match type ({2}). Via {3} {4} => {5}".format(self._path, value, self._type, caller, source, dest))
logger.error("Item '{0}': value ({1}) does not match type ({2}). Via {3} {4} => {5}".format(self._path, value, self._type, caller, source, dest))
return
self._lock.acquire()
if value != self._value or self._enforce_updates: # value change
#logger.debug("update item: %s" % self._path)
if caller != "fade":
self.__fade = False
self._lock.notify_all()
self._change_logger(u"{0} = {1} via {2} {3}".format(self._path, value, caller, source))
self._change_logger("{0} = {1} via {2} {3}".format(self._path, value, caller, source))
self._value = value
delta = self._sh.now() - self._last_change
self._prev_change = delta.seconds + delta.days * 24 * 3600 # FIXME change to timedelta.total_seconds()
Expand All @@ -250,7 +259,7 @@ def _update(self, value=None, caller='Logic', source=None, dest=None):
for update_plugin in self._methods_to_trigger:
try:
update_plugin(self, caller, source, dest)
except Exception, e:
except Exception as e:
logger.error("Problem running {0}: {1}".format(update_plugin, e))
if self._threshold and self._logics_to_trigger:
if self._th and self._value <= self._th_low: # cross lower bound
Expand Down Expand Up @@ -291,8 +300,8 @@ def __getitem__(self, item):

def _db_read(self):
try:
with open(self._sh._cache_dir + self._path, 'r') as f:
return cPickle.load(f)
with open(self._sh._cache_dir + self._path, 'rb') as f:
return pickle.load(f)
except IOError:
logger.info("Could not read {0}{1}".format(self._sh._cache_dir, self._path))
try:
Expand All @@ -308,8 +317,8 @@ def _db_read(self):

def _db_update(self, value):
try:
with open(self._sh._cache_dir + self._path, 'w') as f:
cPickle.dump(value, f)
with open(self._sh._cache_dir + self._path, 'wb') as f:
pickle.dump(value, f)
except IOError:
logger.warning("Could not write to {0}{1}".format(self._sh._cache_dir, self._path))

Expand All @@ -320,7 +329,7 @@ def _db_update(self, value):
def id(self):
return self._path

def __nonzero__(self):
def __bool__(self):
return self._value

def __str__(self):
Expand All @@ -337,7 +346,7 @@ def _trigger_logics(self):
logic.trigger('Item', self._path, self._value)

def _return_str(self, value):
if isinstance(value, basestring):
if isinstance(value, str):
return value
else:
raise ValueError
Expand All @@ -358,14 +367,14 @@ def _return_foo(self, value):
return value

def _return_bool(self, value):
if type(value) in [bool, int, long, float]:
if type(value) in [bool, int, float]:
if value in [False, 0]:
return False
elif value in [True, 1]:
return True
else:
raise ValueError
elif type(value) in [str, unicode]:
elif type(value) in [str, str]:
if value.lower() in ['0', 'false', 'no', 'off']:
return False
elif value.lower() in ['1', 'true', 'yes', 'on']:
Expand Down
4 changes: 2 additions & 2 deletions lib/log.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp marcus@popp.mx
Expand Down Expand Up @@ -41,7 +41,7 @@ def last(self, number):
return(list(self)[-number:])

def export(self, number):
return map(lambda x: self.log_string.format(*x), list(self)[:number])
return [self.log_string.format(*x) for x in list(self)[:number]]

def clean(self, dt):
while True:
Expand Down
13 changes: 7 additions & 6 deletions lib/logic.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011-2013 Marcus Popp marcus@popp.mx
Expand All @@ -21,7 +21,8 @@

import logging
import os
import configobj

import lib.config

logger = logging.getLogger('')

Expand All @@ -37,8 +38,8 @@ def __init__(self, smarthome, configfile):
self.alive = True
logger.debug("reading logics from %s" % configfile)
try:
self._config = configobj.ConfigObj(configfile, file_error=True)
except Exception, e:
self._config = lib.config.parse(configfile)
except Exception as e:
logger.critical(e)
return
for name in self._config:
Expand Down Expand Up @@ -118,7 +119,7 @@ def generate_bytecode(self):
return
try:
self.bytecode = compile(open(filename).read(), self.filename, 'exec')
except Exception, e:
logger.warning("Exception: %s" % e)
except Exception as e:
logger.exception("Exception: %s" % e)
else:
logger.warning("%s: No filename specified => ignoring." % self.name)
10 changes: 5 additions & 5 deletions lib/my_asynchat.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp marcus@popp.mx
Expand Down Expand Up @@ -38,7 +38,7 @@ def __init__(self, smarthome, host='127.0.0.1', port=4711):
asynchat.async_chat.__init__(self, map=smarthome.socket_map)
self.addr = (host, int(port))
self._send_lock = threading.Lock()
self.buffer = ''
self.buffer = bytearray()
self.terminator = '\r\n'
self.is_connected = False
self._sh = smarthome
Expand All @@ -49,7 +49,7 @@ def __init__(self, smarthome, host='127.0.0.1', port=4711):
def connect(self):
self._conn_lock.acquire()
if self.is_connected: # only allow one connection at a time
logger.debug("Don't be hasty. Reduce connection attempts from: {0}".format(self.__class__.__name__))
logger.debug("Don't be hasty. Reduce connection attempts of: {0}".format(self.__class__.__name__))
self._conn_lock.release()
return
try:
Expand All @@ -67,12 +67,12 @@ def connect(self):
self.is_connected = True
self.handle_connect_event()
logger.info('{0}: connected to {1}:{2}'.format(self.__class__.__name__, self.addr[0], self.addr[1]))
except Exception, err:
except Exception as err:
self.handle_exception(err)
self._conn_lock.release()

def collect_incoming_data(self, data):
self.buffer += data
self.buffer.extend(data)

def initiate_send(self):
self._send_lock.acquire()
Expand Down
6 changes: 3 additions & 3 deletions lib/orb.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
#
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
Expand Down Expand Up @@ -27,7 +27,7 @@

try:
import ephem
except ImportError, e:
except ImportError as e:
ephem = None # noqa

import dateutil.relativedelta
Expand All @@ -38,7 +38,7 @@ class Orb():

def __init__(self, orb, lon, lat, elev=False):
if ephem is None:
logger.warning("Could not find/use pyephem!")
logger.warning("Could not find/use ephem!")
return
self._obs = ephem.Observer()
self._obs.long = str(lon)
Expand Down
23 changes: 12 additions & 11 deletions lib/plugin.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011-2013 Marcus Popp marcus@popp.mx
Expand All @@ -21,7 +21,8 @@

import logging
import threading
import configobj

import lib.config

logger = logging.getLogger('')

Expand All @@ -32,28 +33,28 @@ class Plugins():

def __init__(self, smarthome, configfile):
try:
_conf = configobj.ConfigObj(configfile, file_error=True)
except IOError, e:
_conf = lib.config.parse(configfile)
except IOError as e:
logger.critical(e)
return

for plugin in _conf:
args = ''
logger.debug("Plugin: %s" % plugin)
logger.debug("Plugin: {0}".format(plugin))
for arg in _conf[plugin]:
if arg != 'class_name' and arg != 'class_path':
value = _conf[plugin][arg]
if isinstance(value, str):
value = "'%s'" % value
args = args + ", %s=%s" % (arg, value)
value = "'{0}'".format(value)
args = args + ", {0}={1}".format(arg, value)
classname = _conf[plugin]['class_name']
classpath = _conf[plugin]['class_path']
try:
plugin_thread = Plugin(smarthome, plugin, classname, classpath, args)
self._threads.append(plugin_thread)
self._plugins.append(plugin_thread.plugin)
except Exception, e:
logger.warning("Plugin {0} exception: {1}".format(plugin, e))
except Exception as e:
logger.exception("Plugin {0} exception: {1}".format(plugin, e))
del(_conf) # clean up

def __iter__(self):
Expand All @@ -75,8 +76,8 @@ class Plugin(threading.Thread):

def __init__(self, smarthome, name, classname, classpath, args):
threading.Thread.__init__(self, name=name)
exec "import %s" % classpath
exec "self.plugin = %s.%s(smarthome%s)" % (classpath, classname, args)
exec("import {0}".format(classpath))
exec("self.plugin = {0}.{1}(smarthome{2})".format(classpath, classname, args))
setattr(smarthome, self.name, self.plugin)

def run(self):
Expand Down
4 changes: 2 additions & 2 deletions lib/scene.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2013-2013 Marcus Popp marcus@popp.mx
Expand Down Expand Up @@ -54,7 +54,7 @@ def __init__(self, smarthome):
self._scenes[item.id()][row[0]] = [[ditem, row[2]]]
else:
self._scenes[item.id()] = {row[0]: [[ditem, row[2]]]}
except Exception, e:
except Exception as e:
logger.warning("Problem reading scene file {0}: {1}".format(scene_file, e))
continue
item.add_trigger_method(self._trigger)
Expand Down
34 changes: 17 additions & 17 deletions lib/scheduler.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011-2013 Marcus Popp marcus@popp.mx
Expand All @@ -24,7 +24,7 @@
import datetime
import calendar
import sys
import Queue
import queue
import traceback
import threading
import os # noqa
Expand All @@ -46,8 +46,8 @@ class Scheduler(threading.Thread):
_worker_max = 30
_worker_delta = 60 # wait 60 seconds before adding another worker thread
_scheduler = {}
_runq = Queue.PriorityQueue()
_triggerq = Queue.PriorityQueue()
_runq = queue.PriorityQueue()
_triggerq = queue.PriorityQueue()

def __init__(self, smarthome):
threading.Thread.__init__(self, name='Scheduler')
Expand All @@ -72,12 +72,12 @@ def run(self):
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 tn.items()))
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, e:
except Exception as e:
logger.warning("Trigger queue exception: {0}".format(e))
break

Expand Down Expand Up @@ -120,7 +120,7 @@ def trigger(self, name, obj=None, by='Logic', source=None, value=None, dest=None
logger.debug("Logic '{0}' deactivated. Ignoring trigger from {1} {2}".format(name, by, source))
return
if dt is None:
logger.debug(u"Triggering {0} - by: {1} source: {2} dest: {3} value: {4}".format(name, by, source, dest, unicode(value)[:40]))
logger.debug("Triggering {0} - by: {1} source: {2} dest: {3} value: {4}".format(name, by, source, dest, str(value)[:40]))
self._runq.put((prio, name, obj, by, source, dest, value))
else:
if not isinstance(dt, datetime.datetime):
Expand All @@ -129,7 +129,7 @@ def trigger(self, name, obj=None, by='Logic', source=None, value=None, dest=None
if dt.tzinfo is None:
logger.warning("Trigger: Not a valid timezone aware datetime for {0}. Ignoring.".format(name))
return
logger.debug(u"Triggering {0} - by: {1} source: {2} dest: {3} value: {4} at: {5}".format(name, by, source, dest, unicode(value)[:40], dt))
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.put((dt, prio, name, obj, by, source, dest, value))

def remove(self, name):
Expand Down Expand Up @@ -213,7 +213,7 @@ def _next_time(self, name, offset=None):
now = self._sh.now()
now = now.replace(microsecond=0)
if job['cycle'] is not None:
cycle = job['cycle'].keys()[0]
cycle = list(job['cycle'].keys())[0]
value = job['cycle'][cycle]
if offset is None:
offset = cycle
Expand Down Expand Up @@ -266,7 +266,7 @@ def _worker(self):
try:
prio, name, obj, by, source, dest, value = self._runq.get(timeout=0.5)
self._runq.task_done()
except Queue.Empty:
except queue.Empty:
continue
self._task(name, obj, by, source, dest, value)

Expand All @@ -282,24 +282,24 @@ def _task(self, name, obj, by, source, dest, value):
except SystemExit:
# ignore exit() call from logic.
pass
except Exception, e:
except Exception as e:
tb = sys.exc_info()[2]
tb = traceback.extract_tb(tb)[-1]
logger.warning("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, e:
except Exception as e:
logger.warning("Item {0} exception: {1}".format(name, e))
else: # method
try:
if value is None:
obj()
else:
obj(**value)
except Exception, e:
logger.warning("Method {0} exception: {1}".format(name, e))
except Exception as e:
logger.exception("Method {0} exception: {1}".format(name, e))
threading.current_thread().name = 'idle'

def _crontab(self, crontab):
Expand All @@ -325,9 +325,9 @@ def _parse_month(self, crontab, offset=0):
day_range = self._day_range(wday)
elif wday != '*' and day != '*':
day_range = self._day_range(wday)
day_range = day_range + self._range(day, 01, mdays)
day_range = day_range + self._range(day, 0o1, mdays)
else:
day_range = self._range(day, 01, mdays)
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 offset: # next month
Expand Down Expand Up @@ -423,7 +423,7 @@ def _range(self, entry, low, high):
result = []
item_range = []
if entry == '*':
item_range = range(low, high + 1)
item_range = list(range(low, high + 1))
else:
for item in entry.split(','):
item = int(item)
Expand Down
12 changes: 6 additions & 6 deletions lib/tools.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp marcus@popp.mx
Expand All @@ -24,7 +24,7 @@
import datetime
import subprocess
import base64
import httplib
import http.client

logger = logging.getLogger('')

Expand Down Expand Up @@ -60,14 +60,14 @@ def fetch_url(self, url, username=None, password=None, timeout=2):
host = lurl[2]
purl = '/' + '/'.join(lurl[3:])
if plain:
conn = httplib.HTTPConnection(host, timeout=timeout)
conn = http.client.HTTPConnection(host, timeout=timeout)
else:
conn = httplib.HTTPSConnection(host, timeout=timeout)
conn = http.client.HTTPSConnection(host, timeout=timeout)
if username and password:
headers['Authorization'] = 'Basic ' + base64.b64encode(username + ':' + password)
headers['Authorization'] = ('Basic '.encode() + base64.b64encode((username + ':' + password).encode()))
try:
conn.request("GET", purl, headers=headers)
except Exception, e:
except Exception as e:
logger.warning("Problem fetching {0}: {1}".format(url, e))
conn.close()
return False
Expand Down
8 changes: 4 additions & 4 deletions plugins/artnet/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# coding=utf-8
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
Expand Down Expand Up @@ -45,11 +45,11 @@ def stop(self):
self.close()

def __call__(self,var1=None, var2=None):
if type(var1) == types.IntType and type(var2) == types.IntType:
if type(var1) == int and type(var2) == int:
self.send_single_value(var1, var2)
if type(var1) == types.IntType and type(var2) == types.ListType:
if type(var1) == int and type(var2) == list:
self.send_frame_starting_at(var1, var2)
if type(var1) == types.ListType and type(var2) == types.NoneType:
if type(var1) == list and type(var2) == type(None):
self.send_frame(var1)

def send_single_value(self,adr,value):
Expand Down
18 changes: 9 additions & 9 deletions plugins/asterisk/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp
Expand Down Expand Up @@ -33,7 +33,7 @@ class Asterisk(lib.my_asynchat.AsynChat):

def __init__(self, smarthome, username, password, host='127.0.0.1', port=5038):
lib.my_asynchat.AsynChat.__init__(self, smarthome, host, port)
self.terminator = '\r\n\r\n'
self.terminator = '\r\n\r\n'.encode()
self._init_cmd = {'Action': 'Login', 'Username': username, 'Secret': password, 'Events': 'call,user,cdr'}
self._sh = smarthome
self._reply_lock = threading.Condition()
Expand All @@ -59,7 +59,7 @@ def _command(self, d, reply=True):
d['ActionID'] = self._aid
#logger.debug("Request {0} - sending: {1}".format(self._aid, d))
self._reply_lock.acquire()
self.push('\r\n'.join(['{0}: {1}'.format(key, value) for (key, value) in d.items()]) + '\r\n\r\n')
self.push(('\r\n'.join(['{0}: {1}'.format(key, value) for (key, value) in list(d.items())]) + '\r\n\r\n').encode())
if reply:
self._reply_lock.wait(2)
self._reply_lock.release()
Expand All @@ -82,13 +82,13 @@ def db_write(self, key, value):
fam, sep, key = key.partition('/')
try:
return self._command({'Action': 'DBPut', 'Family': fam, 'Key': key, 'Val': value})
except Exception, e:
except Exception as e:
logger.warning("Asterisk: Problem updating {0}/{1} to {2}: {3}.".format(fam, key, value, e))

def mailbox_count(self, mailbox, context='default'):
try:
return self._command({'Action': 'MailboxCount', 'Mailbox': mailbox + '@' + context})
except Exception, e:
except Exception as e:
logger.warning("Asterisk: Problem reading mailbox count {0}@{1}: {2}.".format(mailbox, context, e))
return (0, 0)

Expand All @@ -98,7 +98,7 @@ def call(self, source, dest, context, callerid=None):
cmd['Callerid'] = callerid
try:
self._command(cmd, reply=False)
except Exception, e:
except Exception as e:
logger.warning("Asterisk: Problem calling {0} from {1} with context {2}: {3}.".format(dest, source, context, e))

def hangup(self, hang):
Expand All @@ -111,8 +111,8 @@ def hangup(self, hang):
self._command({'Action': 'Hangup', 'Channel': channel}, reply=False)

def found_terminator(self):
data = self.buffer
self.buffer = ''
data = self.buffer.decode()
self.buffer = bytearray()
event = {}
for line in data.splitlines():
key, sep, value = line.partition(': ')
Expand Down Expand Up @@ -198,7 +198,7 @@ def _update_devices(self):
active_channels = self._command({'Action': 'CoreShowChannels'})
if active_channels is None:
active_channels = []
active_devices = map(self._get_device, active_channels)
active_devices = list(map(self._get_device, active_channels))
for device in self._devices:
if device not in active_devices:
self._devices[device](False, 'Asterisk')
Expand Down
8 changes: 4 additions & 4 deletions plugins/asterisk/agi/name.py
Expand Up @@ -5,8 +5,8 @@

import sys
import re
import urllib
import urllib2
import urllib.request, urllib.parse, urllib.error
import urllib.request, urllib.error, urllib.parse

while 1:
line = sys.stdin.readline()
Expand All @@ -18,9 +18,9 @@
if agi_callerid == 'unknown':
sys.exit()

number = urllib.quote(agi_callerid)
number = urllib.parse.quote(agi_callerid)
exp = re.compile('<[^>]*id="name0"[^>]*>([^<]+)<', re.MULTILINE)
lookup = urllib2.urlopen("http://www3.dastelefonbuch.de/?kw={0}&cmd=search".format(number), data="User-Agent: Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11", timeout=1)
lookup = urllib.request.urlopen("http://www3.dastelefonbuch.de/?kw={0}&cmd=search".format(number), data="User-Agent: Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11", timeout=1)
name = exp.search(lookup.read())
if name != None:
name = name.group(1).strip()
Expand Down
12 changes: 6 additions & 6 deletions plugins/boxcar/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand All @@ -20,8 +20,8 @@
#########################################################################

import logging
import urllib
import httplib
import urllib.request, urllib.parse, urllib.error
import http.client

# boxcar notifications
logger = logging.getLogger('Boxcar')
Expand Down Expand Up @@ -61,14 +61,14 @@ def __call__(self, sender='', message='', link_url=None, icon=None, email=None,
self.__set_path(apikey)

try:
conn = httplib.HTTPSConnection(self._apiurl)
conn.request("POST", self._path, urllib.urlencode(data), self._headers)
conn = http.client.HTTPSConnection(self._apiurl)
conn.request("POST", self._path, urllib.parse.urlencode(data), self._headers)
response = conn.getresponse()
if response.status == 200:
logger.info("Boxcar: Message %s %s successfully sent - %s %s"%(sender, message,response.status,response.reason))
else:
logger.warning("Boxcar: Could not send message %s %s - %s %s"%(sender, message,response.status,response.reason))
conn.close()
del(conn)
except Exception, e:
except Exception as e:
logger.warning("Could not send boxcar notification: {0}. Error: {1}".format(event, e))
21 changes: 12 additions & 9 deletions plugins/cli/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 Marcus Popp
Expand Down Expand Up @@ -29,30 +29,33 @@


class CLIHandler(asynchat.async_chat):
terminator = '\n'
terminator = '\n'.encode()

def __init__(self, smarthome, sock, source, updates):
asynchat.async_chat.__init__(self, sock=sock, map=smarthome.socket_map)
self.source = source
self.updates_allowed = updates
self.sh = smarthome
self._lock = threading.Lock()
self.buffer = ''
self.push("SmartHome.py v%s\n" % self.sh.version)
self.buffer = bytearray()
self.push("SmartHome.py v{0}\n".format(self.sh.version))
self.push("Enter 'help' for a list of available commands.\n")
self.push("> ")

def push(self, data):
asynchat.async_chat.push(self, data.encode())

def collect_incoming_data(self, data):
self.buffer += data
self.buffer.extend(data)

def initiate_send(self):
self._lock.acquire()
asynchat.async_chat.initiate_send(self)
self._lock.release()

def found_terminator(self):
cmd = self.buffer.strip()
self.buffer = ''
cmd = self.buffer.decode().strip()
self.buffer = bytearray()
if cmd.startswith('ls'):
self.push("Items:\n======\n")
self.ls(cmd.lstrip('ls').strip())
Expand Down Expand Up @@ -98,7 +101,7 @@ def ls(self, path):
item = self.sh.return_item(path)
if hasattr(item, 'id'):
if item._type:
self.push(u"{0} = {1}\n".format(item.id(), item()))
self.push("{0} = {1}\n".format(item.id(), item()))
else:
self.push("%s\n" % (item.id()))
for child in item:
Expand All @@ -110,7 +113,7 @@ def la(self):
self.push("Items:\n======\n")
for item in self.sh.return_items():
if item._type:
self.push(u"{0} = {1}\n".format(item.id(), item()))
self.push("{0} = {1}\n".format(item.id(), item()))
else:
self.push("{0}\n".format(item.id()))

Expand Down
8 changes: 4 additions & 4 deletions plugins/dmx/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2011 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand Down Expand Up @@ -50,8 +50,8 @@ def __init__(self, smarthome, tty, interface='nanodmx'):
elif interface == 'enttec':
self._enttec_data = [chr(0)] * 513
self.send = self.send_enttec
self._send_enttec(chr(03) + chr(02) + chr(0) + chr(0) + chr(0))
self._send_enttec(chr(10) + chr(02) + chr(0) + chr(0) + chr(0))
self._send_enttec(chr(0o3) + chr(0o2) + chr(0) + chr(0) + chr(0))
self._send_enttec(chr(10) + chr(0o2) + chr(0) + chr(0) + chr(0))
else:
logger.error("Unknown interface: {0}".format(interface))

Expand Down Expand Up @@ -98,7 +98,7 @@ def parse_item(self, item):
channels = item.conf['dmx_ch']
if isinstance(channels, str):
channels = [channels, ]
channels = map(int, channels)
channels = list(map(int, channels))
item.conf['dmx_ch'] = channels
return self.update_item
else:
Expand Down
28 changes: 14 additions & 14 deletions plugins/dwd/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand Down Expand Up @@ -46,21 +46,21 @@ def __init__(self, smarthome, username, password=True):
self.lock = threading.Lock()
self.tz = dateutil.tz.gettz('Europe/Berlin')
try:
warnings = csv.reader(open(self._warnings_csv, "rb"), delimiter=';')
except IOError, e:
warnings = csv.reader(open(self._warnings_csv, "r"), delimiter=';')
except IOError as e:
logger.error('Could not open warning catalog %s: %s' % (self._warnings_csv, e))
for row in warnings:
self._warning_cat[int(row[0])] = {'summary': unicode(row[1], 'utf-8'), 'kind': unicode(row[2], 'utf-8')}
self._warning_cat[int(row[0])] = {'summary': row[1], 'kind': row[2]}

def _connect(self):
# open ftp connection to dwd
if not hasattr(self, '_ftp'):
try:
self._ftp = ftplib.FTP(self._dwd_host, self._dwd_user, self._dwd_password, timeout=3)
except (socket.error, socket.gaierror), e:
except (socket.error, socket.gaierror) as e:
logger.error('Could not connect to %s: %s' % (self._dwd_host, e))
self.ftp_quit()
except ftplib.error_perm, e:
except ftplib.error_perm as e:
logger.error('Could not login: %s' % e)
self.ftp_quit()

Expand All @@ -86,18 +86,18 @@ def parse_logic(self, logic):
return None

def _buffer_file(self, data):
self._buffer += data
self._buffer.extend(data)

def _retr_file(self, filename):
self.lock.acquire()
self._connect()
self._buffer = ''
self._buffer = bytearray()
try:
self._ftp.retrbinary("RETR %s" % filename, self._buffer_file)
except Exception, e:
self._ftp.retrbinary("RETR {}".format(filename), self._buffer_file)
except Exception as e:
logger.info("problem fetching {0}: {1}".format(filename, e))
self.lock.release()
return self._buffer
return self._buffer.decode()

def _retr_list(self, dirname):
self.lock.acquire()
Expand All @@ -116,7 +116,7 @@ def warnings(self, region, location):
files = self._retr_list(filepath)
for filename in files:
fb = self._retr_file(filename)
fb = fb.decode('iso-8859-1')
# fb = fb.decode('iso-8859-1')
dates = re.findall(r"\d\d\.\d\d\.\d\d\d\d \d\d:\d\d", fb)
now = datetime.datetime.now(self.tz)
if len(dates) > 1: # Entwarnungen haben nur ein Datum
Expand All @@ -139,7 +139,7 @@ def current(self, location):
directory = 'gds/specials/observations/tables/germany'
last = sorted(self._retr_list(directory)).pop()
fb = self._retr_file(last)
fb = fb.decode('iso-8859-1')
# fb = fb.decode('iso-8859-1')
fb = fb.splitlines()
if len(fb) < 8:
logger.info("problem fetching {0}".format(last))
Expand All @@ -162,7 +162,7 @@ def forecast(self, region, location):
for frame in frames:
filepath = "{0}{1}_{2}".format(path, region, frame)
fb = self._retr_file(filepath)
fb = fb.decode('iso-8859-1')
# fb = fb.decode('iso-8859-1')
minute = 0
if frame.count('frueh'):
hour = 6
Expand Down
8 changes: 4 additions & 4 deletions plugins/ebus/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand Down Expand Up @@ -74,7 +74,7 @@ def request(self, request):
try:
self._sock.send(request)
logger.debug("REQUEST: {0}".format(request))
except Exception, e:
except Exception as e:
self._lock.release()
self.close()
logger.warning("error sending request: {0} => {1}".format(request, e))
Expand All @@ -85,7 +85,7 @@ def request(self, request):
self._lock.release()
logger.warning("error receiving answer: timeout")
return
except Exception, e:
except Exception as e:
self._lock.release()
self.close()
logger.warning("error receiving answer: {0}".format(e))
Expand All @@ -99,7 +99,7 @@ def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.settimeout(2)
self._sock.connect((self.host, self.port))
except Exception, e:
except Exception as e:
self._connection_attempts -= 1
if self._connection_attempts <= 0:
logger.error('eBus: could not connect to {0}:{1}: {2}'.format(self.host, self.port, e))
Expand Down
6 changes: 3 additions & 3 deletions plugins/ecmd/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2013 Dirk Wallmeier dirk@wallmeier.info
Expand Down Expand Up @@ -51,7 +51,7 @@ def connect(self):
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._sock.settimeout(2)
self._sock.connect((self.host, self.port))
except Exception, e:
except Exception as e:
self._connection_attempts -= 1
if self._connection_attempts <= 0:
logger.error('ecmd1wire: could not connect to {0}:{1}: {2}'.format(self.host, self.port, e))
Expand All @@ -78,7 +78,7 @@ def request(self):
self._lock.acquire()
try:
self._sock.send("1w list\n")
except Exception, e:
except Exception as e:
self._lock.release()
raise owex("error sending request: {0}".format(e))
table = {}
Expand Down
18 changes: 9 additions & 9 deletions plugins/eta_pu/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#
# Copyright 2013 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand All @@ -21,8 +21,8 @@

import logging
import base64
import httplib
import urllib
import http.client
import urllib.request, urllib.parse, urllib.error
import xml.etree.ElementTree as ET

__ETA_PU__ = 'eta_pu'
Expand All @@ -49,25 +49,25 @@ def __request__(self, req_type, url, timeout=2, username=None, password=None, va
purl = '/' + '/'.join(lurl[3:])
# select protocol: http or https
if url.startswith('https'):
conn = httplib.HTTPSConnection(host, timeout=timeout)
conn = http.client.HTTPSConnection(host, timeout=timeout)
else:
conn = httplib.HTTPConnection(host, timeout=timeout)
conn = http.client.HTTPConnection(host, timeout=timeout)
# add headers
hdrs = { 'Accept': 'text/plain' }
if username and password:
hdrs['Authorization'] = 'Basic ' + base64.b64encode(username + ':' + password)

if 'POST' == req_type:
hdrs['Content-Type'] = 'application/x-www-form-urlencoded'
conn.request(req_type, purl, urllib.urlencode({'@value': value}), hdrs)
conn.request(req_type, purl, urllib.parse.urlencode({'@value': value}), hdrs)
elif 'PUT' == req_type:
conn.request(req_type, purl, body=value, headers=hdrs)
else: # 'GET' or 'DELETE'
conn.request(req_type, purl, headers=hdrs)
resp = conn.getresponse()
conn.close()
# success status: 201/Created for PUT request, else 200/Ok
if resp.status in (httplib.OK, httplib.CREATED):
if resp.status in (http.client.OK, http.client.CREATED):
return resp.read()
logger.warning("request failed: {0}: ".format(url))
logger.debug("{0} response: {1} {2}".format(req_type, resp.status, resp.reason))
Expand Down Expand Up @@ -123,7 +123,7 @@ def parse_item(self, item):
if 'eta_pu_type' not in item.conf:
return None
parent_item = item.return_parent()
if (parent_item == None) or (False == parent_item.conf.has_key('eta_pu_uri')):
if (parent_item == None) or (False == ('eta_pu_uri' in parent_item.conf)):
logger.error("skipping item: found 'eta_pu_type' but parent has no 'eta_pu_uri'")
return
uri = parent_item.conf['eta_pu_uri']
Expand All @@ -142,7 +142,7 @@ def update_status(self):
root = ET.fromstring(xml)

for elem in root.iter():
for key in self._uri.keys():
for key in list(self._uri.keys()):
try:
if key == elem.attrib['uri']:
for i in self._uri[key]:
Expand Down
300 changes: 150 additions & 150 deletions plugins/fritzbox/__init__.py
@@ -1,150 +1,150 @@
#!/usr/bin/env python
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
#########################################################################
# This file is part of SmartHome.py. http://smarthome.sourceforge.net/
#
# 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 sys
import logging
import httplib
import urllib
import hashlib
import re

logger = logging.getLogger('')

class fbex(Exception):
pass

class FritzBoxBase():

def __init__(self, host='fritz.box', password=None):
self._host = host
self._password = password
self._sid = 0

def _login(self):
params = urllib.urlencode({'getpage': '../html/login_sid.xml', 'sid': self._sid})
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
con = httplib.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("no connection to fritzbox.")
data = resp.read()
logger.debug("data = {0}".format(data))
sid = re.search('<SID>(.*?)</SID>', data).group(1)
logger.debug("sid = {0}".format(sid))
if sid == '0000000000000000':
logger.debug("invalid sid, starting challenge/response")
challenge = re.search('<Challenge>(.*?)</Challenge>', data).group(1)
challenge_resp = (challenge + '-' + self._password).decode('iso-8859-1').encode('utf-16le')
m = hashlib.md5()
m.update(challenge_resp)
challenge_resp = challenge + '-' + m.hexdigest().lower()
params = urllib.urlencode({'login:command/response': challenge_resp, 'getpage': '../html/login_sid.xml'})
con = httplib.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("challenge/response failed")
data = resp.read()
self._sid = re.search('<SID>(.*?)</SID>', data).group(1)
logger.debug('session id = {0}'.format(self._sid))

def execute(self, cmd_dict, return_resp=False):
logger.debug("execute command: {0}".format(cmd_dict))
self._login()
cmd_dict['getpage'] = '../html/login_sid.xml'
cmd_dict['sid'] = self._sid
params = urllib.urlencode(cmd_dict)
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
con = httplib.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("execution failed: {0}".format(cmd_dict))
if return_resp:
return resp

def call(self, call_from, call_to):
logger.debug("initiate call from {0} to {1}".format(call_from, call_to))
resp = self.execute({
'telcfg:settings/UseClickToDial': 1,
'telcfg:command/Dial': call_to,
'telcfg:settings/DialPort': call_from
})

class FritzBox(FritzBoxBase):

def __init__(self, smarthome, host='fritz.box', password=None):
FritzBoxBase.__init__(self, host, password)
self._sh = smarthome

def run(self):
self.alive = True

def stop(self):
self.alive = False

def parse_item(self, item):
if 'fritzbox' in item.conf:
return self.update_item
fb = {}
for attr in item.conf:
if attr.startswith('fritzbox'):
fb['telcfg{0}'.format(attr[8:])] = item.conf[attr]
if len(fb) > 0:
item.conf['fritzbox'] = fb
print fb
return self.update_item

def update_item(self, item, caller=None, source=None, dest=None):
if caller != 'Fritzbox' and item():
attr = item.conf['fritzbox']
if isinstance(attr, dict):
self.execute(attr)
return
if isinstance(attr, str):
match = re.search(r'\s*call\s(?P<from>[^\s]+)\s+(?P<to>[^\s]+)\s*', attr)
if match:
self.call(match.group('from'), match.group('to'))
return
logger.debug("fritzbox attribute value {0} on item {1} not recognized".format(attr, item))

def main():
if len(sys.argv) != 4:
print "usage: {0} password from to".format(sys.argv[0])
return 1

handler = logging.StreamHandler(sys.stdout)
frm = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%d.%m.%Y %H:%M:%S")
handler.setFormatter(frm)

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

fb = FritzBoxBase(password=sys.argv[1])
fb.call(sys.argv[2], sys.argv[3])

if __name__ == '__main__':
sys.exit(main())
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
#########################################################################
# This file is part of SmartHome.py. http://smarthome.sourceforge.net/
#
# 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 sys
import logging
import http.client
import urllib.request, urllib.parse, urllib.error
import hashlib
import re

logger = logging.getLogger('')

class fbex(Exception):
pass

class FritzBoxBase():

def __init__(self, host='fritz.box', password=None):
self._host = host
self._password = password
self._sid = 0

def _login(self):
params = urllib.parse.urlencode({'getpage': '../html/login_sid.xml', 'sid': self._sid})
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
con = http.client.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("no connection to fritzbox.")
data = resp.read()
logger.debug("data = {0}".format(data))
sid = re.search('<SID>(.*?)</SID>', data).group(1)
logger.debug("sid = {0}".format(sid))
if sid == '0000000000000000':
logger.debug("invalid sid, starting challenge/response")
challenge = re.search('<Challenge>(.*?)</Challenge>', data).group(1)
challenge_resp = (challenge + '-' + self._password).decode('iso-8859-1').encode('utf-16le')
m = hashlib.md5()
m.update(challenge_resp)
challenge_resp = challenge + '-' + m.hexdigest().lower()
params = urllib.parse.urlencode({'login:command/response': challenge_resp, 'getpage': '../html/login_sid.xml'})
con = http.client.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("challenge/response failed")
data = resp.read()
self._sid = re.search('<SID>(.*?)</SID>', data).group(1)
logger.debug('session id = {0}'.format(self._sid))

def execute(self, cmd_dict, return_resp=False):
logger.debug("execute command: {0}".format(cmd_dict))
self._login()
cmd_dict['getpage'] = '../html/login_sid.xml'
cmd_dict['sid'] = self._sid
params = urllib.parse.urlencode(cmd_dict)
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
con = http.client.HTTPConnection(self._host)
con.request("POST", "/cgi-bin/webcm", params, headers);
resp = con.getresponse()
con.close()
if resp.status != 200:
raise fbex("execution failed: {0}".format(cmd_dict))
if return_resp:
return resp

def call(self, call_from, call_to):
logger.debug("initiate call from {0} to {1}".format(call_from, call_to))
resp = self.execute({
'telcfg:settings/UseClickToDial': 1,
'telcfg:command/Dial': call_to,
'telcfg:settings/DialPort': call_from
})

class FritzBox(FritzBoxBase):

def __init__(self, smarthome, host='fritz.box', password=None):
FritzBoxBase.__init__(self, host, password)
self._sh = smarthome

def run(self):
self.alive = True

def stop(self):
self.alive = False

def parse_item(self, item):
if 'fritzbox' in item.conf:
return self.update_item
fb = {}
for attr in item.conf:
if attr.startswith('fritzbox'):
fb['telcfg{0}'.format(attr[8:])] = item.conf[attr]
if len(fb) > 0:
item.conf['fritzbox'] = fb
print(fb)
return self.update_item

def update_item(self, item, caller=None, source=None, dest=None):
if caller != 'Fritzbox' and item():
attr = item.conf['fritzbox']
if isinstance(attr, dict):
self.execute(attr)
return
if isinstance(attr, str):
match = re.search(r'\s*call\s(?P<from>[^\s]+)\s+(?P<to>[^\s]+)\s*', attr)
if match:
self.call(match.group('from'), match.group('to'))
return
logger.debug("fritzbox attribute value {0} on item {1} not recognized".format(attr, item))

def main():
if len(sys.argv) != 4:
print("usage: {0} password from to".format(sys.argv[0]))
return 1

handler = logging.StreamHandler(sys.stdout)
frm = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%d.%m.%Y %H:%M:%S")
handler.setFormatter(frm)

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

fb = FritzBoxBase(password=sys.argv[1])
fb.call(sys.argv[2], sys.argv[3])

if __name__ == '__main__':
sys.exit(main())
107 changes: 107 additions & 0 deletions plugins/hue/README.md
@@ -0,0 +1,107 @@
---
title: HUE Plugin
layout: default
summary: Plugin to control lamps via Philips hue bridge
---

# Requirements

Needs httplib

## Supported Hardware

Philips hue bridge

# Configuration

## plugin.conf

Typical configuration
<pre>
[HUE]
class_name = HUE
class_path = plugins.hue
hue_user = 38f625a739562a8bd261ab9c7f5e62c8
</pre>

### hue_user
A user name for the hue bridge. Usually this is a hash value of 32 hexadecimal digits.

If the user/hash is not yet authorized, you can use sh.hue.authorizeuser() (via interactive shell or via logic)
to authorize it. The link button must be pressed before.

### hue_ip
IP or host name of the hue bridge. Per default this is "Philips-hue", so that you normally don't have to
specify a value here.

### hue_port
Port number of the hue bridge. Default 80. Normally there is no need to change that.

### cycle
Cycle in seconds to how often update the state of the lights in smarthome.

Note: The hue bridge has no notification feature. Therefore changes can only be detected via polling.

## items.conf

### hue_id

Specify the lamp id. Via this parameter the hue connection
is established.

The feature which is to be controlled is determined
via the type of the item.

type = bool - controls feature 'on' (switching lamp on/off)

type = num - controls feature 'bri' (brightness)

type = dict - controls all features

### hue_feature

Determines which feature to control. If this parameter is given, exactly that
feature is controlled. You have to choose the item type accordingly.

hue_feature = hue - controls the hue. Type must be num

hue_feature = effect - controls the effect. Type must be str.

Special: hue_feature = all - controls all settings via dict.

### Example

<pre>
# items/my.conf

[someroom]
[[mydevice]]
type = bool
hue_id = 1
[[[level]]]
type = num
hue_id = 1
[[[effect]]]
type = str
hue_id = 1
hue_feature = effect
[[[all]]]
type = dict
hue_id = 1
hue_feature = all
</pre>

Hint: on and bri are currently coupled, like a KNX dimmer.

## logic.conf
No logic attributes.


# Methodes

## authorizeuser()
Authorizes the user configured by hue_user config property. You have to press the link button.

<pre>
sh.hue.authorizeuser()
</pre>
220 changes: 220 additions & 0 deletions plugins/hue/__init__.py
@@ -0,0 +1,220 @@
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2013 KNX-User-Forum e.V. http://knx-user-forum.de/
#########################################################################
# This file is part of SmartHome.py. http://smarthome.sourceforge.net/
#
# 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 logging
import json
import http.client
import time

logger = logging.getLogger('')


class HUE():

def __init__(self, smarthome, hue_ip='Philips-hue', hue_user=None, hue_port=80, cycle=10):
self._sh = smarthome
self._hue_ip = hue_ip
self._hue_user = hue_user
self._lamps = {}
self._sh.scheduler.add('hue-update', self._update_lamps, cycle=cycle)
self._sh.trigger('hue-update', self._update_lamps)

def run(self):
self.alive = True
# if you want to create child threads, do not make them daemon = True!
# They will not shutdown properly. (It's a python bug)

def stop(self):
self.alive = False

def parse_item(self, item):
if 'hue_id' in item.conf:
logger.debug("parse item: {0}".format(item))
item.hue_id = int(item.conf['hue_id'])
if 'hue_transitiontime' in item.conf:
item.hue_transitiontime = int(item.conf['hue_transitiontime'])
else:
item.hue_transitiontime = 5
if 'hue_feature' in item.conf:
item.hue_feature = item.conf['hue_feature']
else:
if item._type == 'bool':
item.hue_feature = 'on'
elif item._type == 'num':
item.hue_feature = 'bri'
elif item._type == 'dict':
item.hue_feature = 'all'
else:
logger.error("Can't decide for hue feature based on item type. Item {0}".format(item))
itemkey = item.hue_feature+'items'
if not item.hue_id in self._lamps:
self._lamps[item.hue_id] = {itemkey: [item], 'state':None, 'lastupdate':0}
else:
if itemkey in self._lamps[item.hue_id]:
self._lamps[item.hue_id][itemkey].append(item)
else:
self._lamps[item.hue_id][itemkey] = [item]
# if a state is already available, set it
try:
if item.hue_feature == 'all':
item(self._lamps[item.hue_id]['state'],caller='HUE')
else:
item(self._lamps[item.hue_id]['state'][item.hue_feature],caller='HUE')
except:
pass
return self.update_item
else:
return None

def update_item(self, item, caller=None, source=None, dest=None):
logging.debug("Update: caller {0}".format(caller))
if caller != 'HUE':
logger.info("update item: {0}".format(item.id()))
if item.hue_feature == 'all':
self._set_state(item.hue_id,item())
elif item.hue_feature == 'on':
bri = 254*item()
self._set_state(item.hue_id, {"on": item(), "bri": bri, "transitiontime": item.hue_transitiontime})
for item in self._lamps[item.hue_id]['briitems']:
item(bri, caller='HUE')
elif item.hue_feature == 'bri':
value = item()
if isinstance(value, float):
value = int(value+0.5)
on = value>0
self._set_state(item.hue_id, {"on": on, "bri": value, "transitiontime": item.hue_transitiontime})
for item in self._lamps[item.hue_id]['onitems']:
item(on,caller='HUE')
else:
value = item()
if isinstance(value, float):
value = int(value+0.5)
self._set_state(item.hue_id, {item.hue_feature: value, "transitiontime": item.hue_transitiontime})

def _request(self, path="", method="GET", data=None):
con = http.client.HTTPConnection(self._hue_ip)
con.request(method, "/api/%s%s" % ( self._hue_user, path ), data)
resp = con.getresponse()
con.close()

if resp.status != 200:
logger.error("Request failed")
return None

resp = resp.read()
#logger.debug(resp)

resp = json.loads(resp)

#logger.debug(resp)

if isinstance(resp, list) and resp[0].get("error", None):
error = resp[0]["error"]
if error["type"] == 1:
description = error["description"]
logger.error("Error: {0} (Need to specify correct hue user?)".format(description))
raise Exception("Hue error: {0}".format(description))
else:
return resp

def _set_state(self, light_id, state):
try:
values = self._request("/lights/%s/state" % light_id,
"PUT", json.dumps(state))
# update information database
for part in values:
for status, info in part.items():
if status == 'success':
for path, val in info.items():
parm = path.split('/')[4]
logger.debug("{0} {2} => {1}".format(path,val, parm))
if parm in self._lamps[light_id]['state']:
self._lamps[light_id]['state'][parm] = val
else:
logger.error("hue: {0}: {1}".format(status, info))
raise Exception("Hue error: {0}: {1}".format(status, info))
self._lamps[light_id]['lastupdate'] = time.time()
except Exception:
self._lamps[light_id]['state'] = None # to get an update
return self

def _update_lamps(self):
try:
values = self._request()
for lamp_id, lamp_info in values['lights'].items():
lamp_id = int(lamp_id)
state = lamp_info['state']
logger.debug("Lamp {0}, State {1}".format(lamp_id, state))
try:
lamp = self._lamps[lamp_id]
tick = time.time()
if (tick - lamp['lastupdate'] > 2):
# determine difference
oldstate = self._lamps[lamp_id]['state']
if oldstate is None:
diff = list(state.keys())
else:
diff = set(o for o in state if oldstate[o] != state[o])
# Update the differences
for up in diff:
newval = state[up]
logger.info("New value for {0}: {1}".format(up, newval))
try:
for item in self._lamps[lamp_id][up+'items']:
item(newval, caller='HUE')
except:
pass
if len(diff)>0:
for item in self._lamps[lamp_id]['allitems']:
item(state, caller='HUE')
lamp['state'] = state
lamp['lastupdate'] = tick
except:
# lamp not used, add it to databse
self._lamps[lamp_id] = {'state':state, 'lastupdate':time.time()}
except:
# communication failed
pass

def authorizeuser(self):
data = json.dumps({"devicetype": "smarthome", "username": self._hue_user})

con = http.client.HTTPConnection(self._hue_ip)
con.request("POST", "/api", data);
resp = con.getresponse()
con.close()

if resp.status != 200:
logger.error("Authenticate request failed")
return "Authenticate request failed"

resp = resp.read()
logger.debug(resp)

resp = json.loads(resp)

logger.debug(resp)
return resp

if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
myplugin = HUE('smarthome-dummy')
myplugin.run()
8 changes: 4 additions & 4 deletions plugins/ical/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2013 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand Down Expand Up @@ -60,7 +60,7 @@ def __call__(self, ics, delta=1, offset=0, opt=None):
try:
with open(ics, 'r') as f:
ical = f.read()
except IOError, e:
except IOError as e:
logger.error('Could not open ics file {0}: {1}'.format(ics, e))
return {}
now = self._sh.now()
Expand Down Expand Up @@ -161,7 +161,7 @@ def _parse_ical(self, ical, ics):
elif key in ['DTSTART', 'DTEND', 'EXDATE', 'RECURRENCE-ID']:
try:
date = self._parse_date(val, tzinfo, par)
except Exception, e:
except Exception as e:
logger.warning("Problem parsing: {0}: {1}".format(ics, e))
continue
if key == 'EXDATE':
Expand Down Expand Up @@ -204,7 +204,7 @@ def _parse_rrule(self, event, tzinfo):
if 'UNTIL' in rrule:
try:
rrule['UNTIL'] = self._parse_date(rrule['UNTIL'], tzinfo)
except Exception, e:
except Exception as e:
logger.warning("Problem parsing UNTIL: {1} --- {0} ".format(event, e))
return
for par in rrule:
Expand Down
68 changes: 36 additions & 32 deletions plugins/knx/__init__.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand All @@ -20,11 +20,12 @@
#########################################################################

import logging
import struct
import threading
import struct
import binascii

import lib.my_asynchat
import dpts
from . import dpts

KNXREAD = 0x00
KNXRESP = 0x40
Expand Down Expand Up @@ -54,23 +55,23 @@ def __init__(self, smarthome, time_ga=None, date_ga=None, send_time=False, busmo
smarthome.monitor_connection(self)

def _send(self, data):
send = ''
if len(data) < 2 or len(data) > 0xffff:
logger.debug('Illegal data size: %s' % repr(data))
logger.debug('Illegal data size: {}'.format(repr(data)))
return False
# prepend data length
data = [(len(data) >> 8) & 0xff, (len(data)) & 0xff] + data
for i in data:
send += chr(i)
send = bytearray(len(data).to_bytes(2, byteorder='big'))
send.extend(data)
try:
self._lock.acquire()
self.push(send)
finally:
self._lock.release()

def groupwrite(self, ga, payload, dpt, flag='write'):
pkt = [0, 39] + self.encode(ga, 'ga') + [0]
pkt += self.encode(payload, dpt)
pkt = bytearray([0, 39])
pkt.extend(self.encode(ga, 'ga'))
pkt.extend([0])
pkt.extend(self.encode(payload, dpt))
if flag == 'write':
flag = KNXWRITE
elif flag == 'response':
Expand All @@ -82,11 +83,15 @@ def groupwrite(self, ga, payload, dpt, flag='write'):
self._send(pkt)

def _cacheread(self, ga):
pkt = [0, 116] + self.encode(ga, 'ga') + [0, 0]
pkt = bytearray([0, 116])
pkt.extend(self.encode(ga, 'ga'))
pkt.extend([0, 0])
self._send(pkt)

def groupread(self, ga):
pkt = [0, 39] + self.encode(ga, 'ga') + [0, KNXREAD]
pkt = bytearray([0, 39])
pkt.extend(self.encode(ga, 'ga'))
pkt.extend([0, KNXREAD])
self._send(pkt)

def _send_time(self):
Expand All @@ -105,7 +110,7 @@ def send_time(self, time_ga=None, date_ga=None):

def handle_connect(self):
self.discard_buffers()
enable_cache = [0, 112]
enable_cache = bytearray([0, 112])
self._send(enable_cache)
self.parse_data = self.parse_length
if self._cache_ga != []:
Expand All @@ -115,7 +120,7 @@ def handle_connect(self):
self._cacheread(ga)
self._cache_ga = []
logger.debug('knx: enable group monitor')
init = [0, 38, 0, 0, 0]
init = bytearray([0, 38, 0, 0, 0])
self._send(init)
self.terminator = 2
if self._init_ga != []:
Expand All @@ -126,15 +131,14 @@ def handle_connect(self):
self._init_ga = []

# def collect_incoming_data(self, data):
# ba = bytearray(data)
# print('# bin h d')
# for i in ba:
# for i in data:
# print("{0:08b} {0:02x} {0:02d}".format(i))
# self.buffer += data
# self.buffer.extend(data)

def found_terminator(self):
data = self.buffer
self.buffer = ''
self.buffer = bytearray()
self.parse_data(data)

def parse_length(self, length):
Expand All @@ -151,24 +155,24 @@ def encode(self, data, dpt):
def decode(self, data, dpt):
return dpts.decode[str(dpt)](data)

def parse_telegram(self, telegram):
def parse_telegram(self, data):
self.parse_data = self.parse_length # reset parser and terminator
self.terminator = 2
# 2 byte type
# 2 byte src
# 2 byte dst
# 2 byte command/data
# x byte data
typ = struct.unpack(">H", telegram[:2])[0]
if (typ != 39 and typ != 116) or len(telegram) < 8:
typ = struct.unpack(">H", data[0:2])[0]
if (typ != 39 and typ != 116) or len(data) < 8:
# logger.debug("Ignore telegram.")
return
if (ord(telegram[6]) & 0x03 or (ord(telegram[7]) & 0xC0) == 0xC0):
if (data[6] & 0x03 or (data[7] & 0xC0) == 0xC0):
logger.debug("Unknown APDU")
return
src = self.decode(telegram[2:4], 'pa')
dst = self.decode(telegram[4:6], 'ga')
flg = ord(telegram[7]) & 0xC0
src = self.decode(data[2:4], 'pa')
dst = self.decode(data[4:6], 'ga')
flg = data[7] & 0xC0
if flg == KNXWRITE:
flg = 'write'
elif flg == KNXREAD:
Expand All @@ -178,19 +182,19 @@ def parse_telegram(self, telegram):
else:
logger.warning("Unknown flag: {0:02x} src: {1} dest: {2}".format(flg, src, dst))
return
if len(telegram) == 8:
payload = chr(ord(telegram[7]) & 0x3f)
if len(data) == 8:
payload = bytearray([data[7] & 0x3f])
else:
payload = telegram[8:]
payload = data[8:]
if flg == 'write' or flg == 'response':
if dst not in self.gal: # update item/logic
self._busmonitor("knx: {0} set {1} to {2}".format(src, dst, self.decode(payload, 'hex')))
self._busmonitor("knx: {0} set {1} to {2}".format(src, dst, binascii.hexlify(payload).decode()))
return
dpt = self.gal[dst]['dpt']
try:
val = self.decode(payload, dpt)
except Exception, e:
logger.warning("knx: Problem decoding frame from {0} to {1} with '{2}' and DPT {3}. Exception: {4}".format(src, dst, self.decode(payload, 'hex'), dpt, e))
except Exception as e:
logger.warning("knx: Problem decoding frame from {0} to {1} with '{2}' and DPT {3}. Exception: {4}".format(src, dst, binascii.hexlify(payload).decode(), dpt, e))
return
if val is not None:
self._busmonitor("knx: {0} set {1} to {2}".format(src, dst, val))
Expand All @@ -204,7 +208,7 @@ def parse_telegram(self, telegram):
for logic in self.gal[dst]['logics']:
logic.trigger('KNX', src, val, dst)
else:
logger.warning("Wrong payload '{2}' for ga '{1}' with dpt '{0}'.".format(dpt, dst, self.decode(payload, 'hex')))
logger.warning("Wrong payload '{2}' for ga '{1}' with dpt '{0}'.".format(dpt, dst, binascii.hexlify(payload).decode()))
elif flg == 'read':
logger.debug("{0} read {1}".format(src, dst))
if dst in self.gar: # read item
Expand Down
41 changes: 15 additions & 26 deletions plugins/knx/dpts.py
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2012-2013 KNX-User-Forum e.V. http://knx-user-forum.de/
Expand Down Expand Up @@ -30,7 +30,7 @@ def en1(value):
def de1(payload):
if len(payload) != 1:
return None
return bool(ord(payload) & 0x01)
return bool(payload & 0x01)


def en2(vlist):
Expand All @@ -41,7 +41,6 @@ def en2(vlist):
def de2(payload):
if len(payload) != 1:
return None
payload = ord(payload)
return [int(payload) >> 1 & 0x01, int(payload) & 0x01]


Expand All @@ -53,13 +52,12 @@ def en3(vlist):
def de3(payload):
if len(payload) != 1:
return None
payload = ord(payload)
# up/down, stepping
return [int(payload) >> 3 & 0x01, int(payload) & 0x07]


def en4002(value):
if isinstance(value, unicode):
if isinstance(value, str):
value = value.encode('iso-8859-1')
else:
value = str(value)
Expand Down Expand Up @@ -150,8 +148,8 @@ def en9(value):
def de9(payload):
if len(payload) != 2:
return None
i1 = ord(payload[0])
i2 = ord(payload[1])
i1 = payload[0]
i2 = payload[1]
s = (i1 & 0x80) >> 7
e = (i1 & 0x78) >> 3
m = (i1 & 0x07) << 8 | i2
Expand All @@ -165,10 +163,9 @@ def en10(dt):


def de10(payload):
ba = bytearray(payload)
h = ba[0] & 0x1f
m = ba[1] & 0x3f
s = ba[2] & 0x3f
h = payload[0] & 0x1f
m = payload[1] & 0x3f
s = payload[2] & 0x3f
return datetime.time(h, m, s)


Expand All @@ -177,10 +174,9 @@ def en11(date):


def de11(payload):
ba = bytearray(payload)
d = ba[0] & 0x1f
m = ba[1] & 0x0f
y = (ba[2] & 0x7f) + 2000 # sorry no 20th century...
d = payload[0] & 0x1f
m = payload[1] & 0x0f
y = (payload[2] & 0x7f) + 2000 # sorry no 20th century...
return datetime.date(y, m, d)


Expand Down Expand Up @@ -229,7 +225,7 @@ def de14(payload):


def en16000(value):
if isinstance(value, unicode):
if isinstance(value, str):
value = value.encode('ascii')
else:
value = str(value)
Expand All @@ -242,7 +238,7 @@ def en16000(value):


def en16001(value):
if isinstance(value, unicode):
if isinstance(value, str):
value = value.encode('iso-8859-1')
else:
value = str(value)
Expand Down Expand Up @@ -279,7 +275,7 @@ def de20(payload):


def en24(value):
if isinstance(value, unicode):
if isinstance(value, str):
value = value.encode('iso-8859-1')
else:
value = str(value)
Expand Down Expand Up @@ -322,12 +318,6 @@ def dega(string):
return "{0}/{1}/{2}".format((ga >> 11) & 0x1f, (ga >> 8) & 0x07, (ga) & 0xff)


def dehex(payload):
#xlist = ["{0:02x}".format(ord(char)) for char in payload]
xlist = ["{0:x}".format(ord(char)) for char in payload]
return ' '.join(xlist)


decode = {
'1': de1,
'2': de2,
Expand All @@ -350,8 +340,7 @@ def dehex(payload):
'24': de24,
'232': de232,
'pa': depa,
'ga': dega,
'hex': dehex
'ga': dega
}

encode = {
Expand Down