| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,150 +1,150 @@ | ||
| #!/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()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |