├── .gitignore
├── .travis.yml
├── Example
└── PyDect200_Demo.py
├── LICENSE
├── MANIFEST
├── PyDect200
├── PyDect200.py
└── __init__.py
├── README.md
├── setup.cfg
├── setup.py
└── test_PyDect200.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[cod]
2 |
3 | # C extensions
4 | *.so
5 |
6 | # Packages
7 | *.egg
8 | *.egg-info
9 | dist
10 | build
11 | eggs
12 | parts
13 | bin
14 | var
15 | sdist
16 | develop-eggs
17 | .installed.cfg
18 | lib
19 | lib64
20 | __pycache__
21 |
22 | # Installer logs
23 | pip-log.txt
24 |
25 | # Unit test / coverage reports
26 | .coverage
27 | .tox
28 | nosetests.xml
29 |
30 | # Translations
31 | *.mo
32 |
33 | # Mr Developer
34 | .mr.developer.cfg
35 | .project
36 | .pydevproject
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.6"
4 | - "2.7"
5 | - "3.3"
6 | - "3.4"
7 | - "3.5"
8 | - "3.6"
9 | - "nightly"
10 | - "pypy"
11 | - "pypy3"
12 | matrix:
13 | include:
14 | - python: 3.7
15 | dist: xenial
16 | sudo: true
17 | script: python test_PyDect200.py
--------------------------------------------------------------------------------
/Example/PyDect200_Demo.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -- coding: utf-8 --
3 | from __future__ import (absolute_import, division,
4 | print_function, unicode_literals)
5 |
6 | try:
7 | from PyDect200 import PyDect200
8 | except:
9 | print(u'PyDect200 is not installed!')
10 | print(u'run: pip install PyDect200')
11 | exit()
12 | import getpass
13 |
14 | try:
15 | PyDect200.__version__
16 | except:
17 | PyDect200 = PyDect200.PyDect200
18 |
19 | print(u"Welcome to PyDect200 v%s, the Python AVM-DECT200 API" % PyDect200.__version__)
20 | fritzbox_username = getpass.getpass(prompt='Please insert your fritzbox username (press enter to skip): ', stream=None)
21 | fritzbox_pw = getpass.getpass(prompt='Please insert your fritzbox-password: ', stream=None)
22 | print(u'Thank you, please wait few seconds...')
23 | f = PyDect200(fritzbox_pw, username=fritzbox_username)
24 |
25 | if not f.login_ok():
26 | print("Login Not Successful, Wrong Password?")
27 | exit(1)
28 |
29 | try:
30 | info = f.get_info()
31 | power = f.get_power_all()
32 | names = f.get_device_names()
33 | except Exception:
34 | print(u'HTTP-Error, wrong password?')
35 | exit()
36 |
37 | print(u'')
38 | for dev_id in info.keys():
39 | print(u"Device ID: %s" % dev_id)
40 | dev_name = names.get(dev_id)
41 | try:
42 | print(u"Device Name: %s" % dev_name)
43 | except:
44 | print(u"Device Name: %s" % dev_name.encode('utf-8').decode('utf-8', 'ignore'))
45 |
46 |
47 | print(u"Device State: %s" % ('ON' if info.get(dev_id) == '1' else 'OFF'))
48 | dev_power = power.get(dev_id)
49 | if dev_power.isdigit():
50 | dev_power = float(dev_power) / 1000
51 | print(u"Device Power: %sW" % dev_power)
52 | print(u"Device Energy: %sWh" % f.get_energy_single(dev_id))
53 | print(u"Device Temperature: %s degree Celsius " % (f.get_temperature_single(dev_id)))
54 | print(u'')
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Mathias P.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/MANIFEST:
--------------------------------------------------------------------------------
1 | # file GENERATED by distutils, do NOT edit
2 | setup.cfg
3 | setup.py
4 | PyDect200/PyDect200.py
5 | PyDect200/__init__.py
6 |
--------------------------------------------------------------------------------
/PyDect200/PyDect200.py:
--------------------------------------------------------------------------------
1 | # -- coding: utf-8 --
2 |
3 | """
4 | Module to Control the AVM DECT200 Socket
5 | """
6 |
7 | from __future__ import (absolute_import, division,
8 | print_function, unicode_literals)
9 | import hashlib, sys
10 | try:
11 | import urllib.request as urllib2
12 | except ImportError:
13 | import urllib2
14 |
15 | class PyDect200(object):
16 | """
17 | Class to Control the AVM DECT200 Socket
18 | """
19 | __version__ = u'0.0.16'
20 | __author__ = u'Mathias Perlet'
21 | __author_email__ = u'mathias@mperlet.de'
22 | __description__ = u'Control Fritz AVM DECT200'
23 |
24 | __fritz_url = u'http://fritz.box'
25 | __homeswitch = u'/webservices/homeautoswitch.lua'
26 | __username_query = u''
27 |
28 | __debug = False
29 |
30 | def __init__(self, fritz_password, username = u''):
31 | """The constructor"""
32 | self.__password = fritz_password
33 | if username != u'':
34 | self.__username_query = u'username=%s&' % username
35 | self.get_sid()
36 |
37 | def set_url(self, url):
38 | """Set alternative url"""
39 | self.__fritz_url = url
40 |
41 | def login_ok(self):
42 | """Returns True for a valid session id"""
43 | return self.sid is not None and self.sid != u'0000000000000000'
44 |
45 | def set_debug(self, enable=True):
46 | """Enables some debug prints"""
47 | self.__debug = enable
48 |
49 | def __homeauto_url_with_sid(self):
50 | """Returns formatted uri"""
51 | return u'%s%s?sid=%s' % (self.__fritz_url,
52 | self.__homeswitch,
53 | self.sid)
54 |
55 | @classmethod
56 | def __query(cls, url):
57 | """Reads a URL"""
58 | try:
59 | return urllib2.urlopen(url).read().decode('utf-8').replace('\n', '')
60 | except urllib2.HTTPError:
61 | _, exception, _ = sys.exc_info()
62 | if cls.__debug:
63 | print('HTTPError = ' + str(exception.code))
64 | except urllib2.URLError:
65 | _, exception, _ = sys.exc_info()
66 | if cls.__debug:
67 | print('URLError = ' + str(exception.reason))
68 | except Exception:
69 | _, exception, _ = sys.exc_info()
70 | if cls.__debug:
71 | print('generic exception: ' + str(exception))
72 | raise
73 | pass
74 | return "inval"
75 |
76 |
77 |
78 | def __query_cmd(self, command, device=None):
79 | """Calls a command"""
80 | base_url = u'%s&switchcmd=%s' % (self.__homeauto_url_with_sid(), command)
81 |
82 | if device is None:
83 | url = base_url
84 | else:
85 | url = '%s&ain=%s' % (base_url, device)
86 |
87 | if self.__debug:
88 | print(u'Query Command URI: ' + url)
89 |
90 | return self.__query(url)
91 |
92 | def get_sid(self):
93 | """Returns a valid SID"""
94 | base_url = u'%s/login_sid.lua' % self.__fritz_url
95 | get_challenge = None
96 | try:
97 | get_challenge = urllib2.urlopen(base_url).read().decode('ascii')
98 | except urllib2.HTTPError as exception:
99 | print('HTTPError = ' + str(exception.code))
100 | except urllib2.URLError as exception:
101 | print('URLError = ' + str(exception.reason))
102 | except Exception as exception:
103 | print('generic exception: ' + str(exception))
104 | raise
105 |
106 |
107 | challenge = get_challenge.split(
108 | '')[1].split('')[0]
109 | challenge_b = (
110 | challenge + '-' + self.__password).encode().decode('iso-8859-1').encode('utf-16le')
111 |
112 | md5hash = hashlib.md5()
113 | md5hash.update(challenge_b)
114 |
115 | response_b = challenge + '-' + md5hash.hexdigest().lower()
116 | get_sid = urllib2.urlopen('%s?%sresponse=%s' % (base_url, self.__username_query, response_b)).read().decode('utf-8')
117 | self.sid = get_sid.split('')[1].split('')[0]
118 |
119 | def get_info(self):
120 | """Returns device info"""
121 | return self.get_state_all()
122 |
123 | def switch_onoff(self, device, status):
124 | """Switch a Socket"""
125 | if status == 1 or status == True or status == '1':
126 | return self.switch_on(device)
127 | else:
128 | return self.switch_off(device)
129 |
130 | def switch_toggle(self, device):
131 | """Toggles the current state of the given device"""
132 | state = self.get_state(device)
133 | if(state == '1'):
134 | return self.switch_off(device)
135 |
136 | elif(state == '0'):
137 | return self.switch_on(device)
138 | else:
139 | return state
140 |
141 | def get_power(self):
142 | """Returns the Power in Watt"""
143 | power_dict = self.get_power_all()
144 | for device in power_dict.keys():
145 | power_dict[device] = float(power_dict[device]) / 1000.0
146 | return power_dict
147 |
148 | def get_device_ids(self):
149 | """Returns a list of device id strings"""
150 | return self.__query_cmd('getswitchlist').split(',')
151 |
152 | def get_device_names(self):
153 | """Returns a Dict with devicenames"""
154 | dev_names = {}
155 | for device in self.get_device_ids():
156 | dev_names[device] = self.get_device_name(device)
157 | return dev_names
158 |
159 | def get_device_name(self, device):
160 | """Returns the name for a single device"""
161 | return self.__query_cmd('getswitchname', device)
162 |
163 | def get_power_single(self, device):
164 | """Returns the power in mW for a single device"""
165 | return self.__query_cmd('getswitchpower', device)
166 |
167 | def get_energy_single(self, device):
168 | """Returns the energy in Wh for a single device"""
169 | return self.__query_cmd('getswitchenergy', device)
170 |
171 | def get_temperature_single(self, device):
172 | """Returns the temperature in 0.1 °C for a single device"""
173 | temp_str = self.__query_cmd('gettemperature', device)
174 | if temp_str.lstrip('-').isdigit():
175 | return float(temp_str) / 10.0
176 | return 'inval'
177 |
178 | def get_power_all(self):
179 | """Returns the power in mW for all devices"""
180 | power_dict = {}
181 | for device in self.get_device_names().keys():
182 | power_dict[device] = self.get_power_single(device)
183 | return power_dict
184 |
185 | def switch_on(self, device):
186 | """Switch device on"""
187 | return self.__query_cmd('setswitchon', device)
188 |
189 |
190 | def switch_off(self, device):
191 | """Switch device off"""
192 | return self.__query_cmd('setswitchoff', device)
193 |
194 |
195 | def get_state(self, device):
196 | """Returns the device state"""
197 | return self.__query_cmd('getswitchstate', device)
198 |
199 | def get_state_all(self):
200 | """Returns all device states"""
201 | state_dict = {}
202 | for device in self.get_device_names().keys():
203 | state_dict[device] = self.get_state(device)
204 | return state_dict
205 |
--------------------------------------------------------------------------------
/PyDect200/__init__.py:
--------------------------------------------------------------------------------
1 | from PyDect200 import *
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PyDect200
2 | ======
3 | [](https://travis-ci.org/mperlet/PyDect200)
4 | 
5 | [](https://pypi.python.org/pypi/PY_DECT200/)
6 | [](https://pypi.python.org/pypi/PY_DECT200/)
7 | [](https://pypi.python.org/pypi/PY_DECT200/)
8 | [](https://pypi.python.org/pypi/PY_DECT200/)
9 |
10 |
11 | Control the Fritz-AVM DECT200 (switch a electric socket)
12 | and Fritz-AVM PowerLine 546E
13 |
14 | ### Install
15 |
16 | ```
17 | pip install PyDect200
18 | ```
19 |
20 | ### Demo
21 |
22 | #### Demo (Github Style)
23 |
24 | ```
25 | curl https://raw.githubusercontent.com/mperlet/PyDect200/master/Example/PyDect200_Demo.py | python
26 | ```
27 |
28 | #### Demo (git clone)
29 |
30 | ```
31 | git clone git@github.com:mperlet/PyDect200.git
32 |
33 | ./PyDect200/Example/PyDect200_Demo.py
34 | ```
35 |
36 | ### Example Code
37 |
38 | ```
39 | from PyDect200 import PyDect200
40 | f = PyDect200('fitzbox_password')
41 | # or with username PyDect200('fritzbox_password', username='fritzbox_username')
42 |
43 | f.get_device_names()
44 | # {'16': 'Beleuchtung', '17': 'Fernseher'}
45 |
46 | f.get_info()
47 | # {u'16': u'0', u'17': u'0'}
48 |
49 | f.switch_onoff(16,1)
50 | # {u'DeviceID': u'16',
51 | # u'RequestResult': u'1',
52 | # u'Value': u'0',
53 | # u'ValueToSet': u'1'}
54 |
55 | f.get_power()
56 | # {u'16': 68.95, u'17': 0.0}
57 | ```
58 |
59 | ### Tested with
60 |
61 | * Python2.7 / Python3.4
62 | * Fritzbox 7270
63 | * FRITZ!OS: 06.05
64 | * AVM Dect200
65 |
66 | ******************
67 |
68 | * Python2.7
69 | * Fritzbox 7490
70 | * FRITZ!OS: 6.36 Labor
71 | * Dect200
72 | * PowerLine 546E
73 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from distutils.core import setup
4 | from PyDect200 import PyDect200
5 | try:
6 | PyDect200.__version__
7 | except:
8 | PyDect200 = PyDect200.PyDect200
9 |
10 | setup(name='PyDect200',
11 | version=PyDect200.__version__,
12 | description=PyDect200.__description__,
13 | author=PyDect200.__author__,
14 | author_email=PyDect200.__author_email__,
15 | license='MIT',
16 | url='https://github.com/mperlet/PyDect200',
17 | packages=['PyDect200'],
18 | keywords = ['avm', 'dect200', 'fritzbox', 'dect', 'switch', 'smart home', 'PowerLine 546E'],
19 | )
20 |
--------------------------------------------------------------------------------
/test_PyDect200.py:
--------------------------------------------------------------------------------
1 | from __future__ import (absolute_import, division,
2 | print_function, unicode_literals)
3 |
4 | import sys
5 | import unittest
6 |
7 | from PyDect200.PyDect200 import PyDect200
8 |
9 | try:
10 | from unittest.mock import patch, MagicMock # Py3
11 | except Exception:
12 | from mock import patch, Mock # Py2
13 |
14 | PY2 = 2
15 | PY3 = 3
16 |
17 |
18 | def run(python_version):
19 | def run_decorator(func):
20 | def function_wrapper(x):
21 | if (sys.version_info > (3, 0)) and python_version == PY3:
22 | func(x)
23 | elif (sys.version_info < (3, 0)) and python_version == PY2:
24 | func(x)
25 | else:
26 | return True
27 |
28 | return function_wrapper
29 |
30 | return run_decorator
31 |
32 |
33 | class TestPyDect200(unittest.TestCase):
34 |
35 | @run(PY2)
36 | @patch('PyDect200.urllib2.urlopen')
37 | def test_basic_commands_py2(self, mock_urlopen):
38 | cm = Mock()
39 | cm.read.side_effect = ['1234',
40 | 'caffeaffe1234',
41 | 'device1,device2,device3', 'true']
42 | mock_urlopen.return_value = cm
43 |
44 | instance = PyDect200("testtest")
45 |
46 | self.assertEqual(instance.sid, 'caffeaffe1234')
47 |
48 | self.assertEqual(instance.get_device_ids(),
49 | ['device1', 'device2', 'device3'])
50 | self.assertEqual(instance.switch_onoff('device1', True), 'true')
51 |
52 | @run(PY2)
53 | @patch('PyDect200.PyDect200.get_sid')
54 | def test_session_id_py2(self, mock_sid):
55 | mock_sid.return_value = "hey"
56 |
57 | instance = PyDect200("testtest")
58 | instance.sid = 'caffeaffe1234'
59 | self.assertEqual(instance.sid, 'caffeaffe1234')
60 |
61 | @run(PY3)
62 | @patch('urllib.request.urlopen')
63 | def test_basic_commands_py3(self, mock_urlopen):
64 | cm = MagicMock()
65 | cm.read.side_effect = ['1234'.encode(),
66 | 'caffeaffe1234'.encode(),
67 | 'device1,device2,device3'.encode(),
68 | 'true'.encode()]
69 | mock_urlopen.return_value = cm
70 |
71 | instance = PyDect200("testtest")
72 |
73 | self.assertEqual(instance.sid, 'caffeaffe1234')
74 |
75 | self.assertEqual(instance.get_device_ids(),
76 | ['device1', 'device2', 'device3'])
77 | self.assertEqual(instance.switch_onoff('device1', True), 'true')
78 |
79 |
80 | if __name__ == '__main__':
81 | unittest.main()
82 |
--------------------------------------------------------------------------------