├── .gitignore ├── LICENSE ├── README.md ├── SCHEDFORMAT.md └── src ├── ediplug ├── __init__.py └── smartplug.py ├── examples ├── sample_01.py ├── sample_02.py ├── sample_03.py ├── sample_04.py ├── sample_05.py └── sample_06.py └── utest ├── parse_schedule.py └── render_schedule.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EDIPLUG-PY - Python Class for EDIMAX Smart Plug Switch SP-1101W 2 | =============================================================== 3 | 2016-26-01 Stefan Wendler 4 | sw@kaltpost.de 5 | 6 | Simple Python class to access a "EDIMAX Smart Plug Switch SP1101W/SP2101W". Supports 7 | simple switching as well as programming the schedule of the plug and reading power and 8 | current consumption. The class could be used as an API or as command line utility: 9 | 10 | __Important note__: I think the library does not work with newest FW versions. 11 | Only FW versions which connect to EdiPlug App work. The ones connecting to EdiSmart 12 | use a different protocol. The latest versions working are: 13 | 14 | * SP1101W: v2.02 15 | * SP2101W: v2.03 16 | 17 | Requirements 18 | ------------ 19 | 20 | * Python 2.7 21 | * The requests library (e.g. use `pip install requests`) 22 | 23 | 24 | Use as command line utility 25 | --------------------------- 26 | 27 | General: 28 | 29 | * To specify the hostname or IP address of the plug us the `-H ` parameter. 30 | * To pass along the login for the plug use `-l `. 31 | * To set the password for the login user `-p ` 32 | 33 | Get device info: 34 | 35 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -i 36 | 37 | Turn plug on: 38 | 39 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -s ON 40 | 41 | Turn plug off: 42 | 43 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -s OFF 44 | 45 | Get plug state: 46 | 47 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -g 48 | 49 | Get power consumption (only SP2101W) 50 | 51 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -w 52 | 53 | Get current consumption (only SP2101W) 54 | 55 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -a 56 | 57 | Get schedule of the whole week (human readable): 58 | 59 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -G 60 | 61 | Get schedule of the whole week (python array): 62 | 63 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -P 64 | 65 | Set schedule for one day: 66 | 67 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -S "{'state': u'ON', 'sched': [[[11, 0], [11, 45]]], 'day': 6}" 68 | 69 | Set schedule for the whole week: 70 | 71 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -S "[ 72 | {'state': u'ON', 'sched': [[[1, 0], [1, 1]]], 'day': 0}, 73 | {'state': u'ON', 'sched': [[[2, 0], [2, 2]]], 'day': 1}, 74 | {'state': u'ON', 'sched': [[[3, 0], [3, 3]]], 'day': 2}, 75 | {'state': u'ON', 'sched': [[[4, 0], [4, 4]]], 'day': 3}, 76 | {'state': u'ON', 'sched': [[[5, 0], [5, 5]]], 'day': 4}, 77 | {'state': u'ON', 'sched': [[[6, 0], [6, 6]]], 'day': 5}, 78 | {'state': u'ON', 'sched': [[[7, 0], [7, 7]]], 'day': 6}, 79 | ]" 80 | 81 | 82 | Use as API 83 | ---------- 84 | 85 | For reference, there are the following examples: 86 | 87 | * [turn the plug ON and OFF](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_01.py) 88 | * [query week schedule](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_02.py) 89 | * [set the schedule of one day](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_03.py) 90 | * [set the schedule of one week](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_04.py) 91 | * [read device info](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_05.py) 92 | * [connect the plug to Blynk](https://github.com/wendlers/ediplug-py/blob/master/src/examples/sample_06.py) 93 | 94 | General API usage is as follows. 95 | 96 | Import: 97 | 98 | from ediplug.smartplug import SmartPlug 99 | 100 | Create plug object: 101 | 102 | p = SmartPlug("172.16.100.75", ('admin', '1234')) 103 | 104 | Get device info 105 | 106 | print(p.info) 107 | 108 | Turn plug off: 109 | 110 | p.state = "OFF" 111 | 112 | Turn plug on: 113 | 114 | p.state = "ON" 115 | 116 | Query and print current state of plug: 117 | 118 | print(p.state) 119 | 120 | Get power consumption (only SP2101W) 121 | 122 | print(p.power) 123 | 124 | Get current consumption (only SP2101W) 125 | 126 | print(p.current) 127 | 128 | Read and print complete week schedule from plug: 129 | 130 | print(p.schedule.__str__()) 131 | 132 | Write schedule for on day to plug (Saturday, 11:15 - 11:45): 133 | 134 | p.schedule = {'state': u'ON', 'sched': [[[11, 15], [11, 45]]], 'day': 6} 135 | 136 | Note: 0 - Sunday, 6 - Saturday 137 | 138 | Write schedule for the whole week: 139 | 140 | p.schedule = [ 141 | {'state': u'ON', 'sched': [[[0, 3], [0, 4]]], 'day': 0}, 142 | {'state': u'ON', 'sched': [[[0, 10], [0, 20]], [[10, 16], [11, 55]], [[15, 19], [15, 32]], [[21, 0], [23, 8]], [[23, 17], [23, 59]]], 'day': 1}, 143 | {'state': u'OFF', 'sched': [[[19, 59], [21, 1]]], 'day': 2}, 144 | {'state': u'OFF', 'sched': [[[20, 59], [21, 12]]], 'day': 3}, 145 | {'state': u'OFF', 'sched': [], 'day': 4}, 146 | {'state': u'OFF', 'sched': [[[0, 0], [0, 30]], [[11, 14], [14, 31]]], 'day': 5}, 147 | {'state': u'ON', 'sched': [[[1, 42], [2, 41]]], 'day': 6}] 148 | 149 | 150 | Complete examples could be found in the `examples` directory. Don't forget to set your python path properly. E.g. when 151 | running the 1st exmaple from the projects main directory: 152 | 153 | PYTHONPATH=./src 154 | python ./src/examples/sample_01.py 155 | 156 | 157 | Further Information 158 | ------------------- 159 | 160 | Some more information on the plug and its XML API is provided on my website: 161 | 162 | * [Operating from Python] (http://gpio.kaltpost.de/?p=2112) 163 | * [Scheduling] (http://gpio.kaltpost.de/?p=2224) 164 | 165 | 166 | Change Log 167 | ---------- 168 | * 2018-01-26: added example on how to connect the plug with Blynk 169 | * 2015-12-15: added 'power' and 'current' functionality (SP2101W only, thanks Robin Grundei 170 | for the XML request/response and the testing). 171 | * 2015-10-28: added 'info' property to get device info (thanks Andreas Kainz for the patch!) 172 | * 2014-12-27: re-wrote scheduling to correct various scheduling bugs 173 | * 2014-09-12: first version 174 | 175 | -------------------------------------------------------------------------------- /SCHEDFORMAT.md: -------------------------------------------------------------------------------- 1 | EDIMAX Smart Plug Switch SP-1101W - Scheduling-Format 2 | =============================================================== 3 | 27.12.2014 Stefan Wendler 4 | sw@kaltpost.de 5 | 6 | This document describes the format used to schedule ON/OFF times with the "EDIMAX Smart Plug Switch SP-1101W". 7 | 8 | General XML Format 9 | ------------------ 10 | 11 | When retrieving the schedules from the Plug, the following XML is returned: 12 | 13 | 14 | 15 | 16 | 17 | F80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 18 | 000000000000000FFFFFFFC0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 19 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 20 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFF000000000000000000000000000000000000000000000 21 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 22 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 23 | 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 24 | 25 | 26 | 27 | 28 | For each day an entry like this is included: 29 | 30 | ... 31 | 32 | X = 0 is Sunday, X = 1 is Monday, X = 6 is Saturday. The “value” attribute is “ON” if the schedule for this day is active or “OFF” if disabled. 33 | The data of the “Device.System.Power.Schedule.X” tag is 360 characters long. Each hour is represented by 15 characters, thus 360 characters = 24h. 34 | For further discussion we will focus on that 360 characters (let's call this "packed schedule". 35 | 36 | Packed Schedule Format in Detail 37 | -------------------------------- 38 | 39 | Once again, we have 360 characters representing 24 hours. Thus 15 characters represent 1 hour. This again tells us, 40 | that one characters needs to represent 4 minutes to allow as to represent every minute of an hour. 41 | 42 | The interesting question now is: how is the 60 minute information encoded (packed) into 15 characters? Well this is 43 | very simple. Each character represents four bits. If a bit is set, the minute is set. Then if one transforms each 4-bit 44 | value into its corresponding hex value, and if this hex values are joined together, the 15 character string for each hour 45 | could be build. By joining together all the 15 characters hour values, we get a whole days schedule. 46 | 47 | The above could be best illustrated by example. 48 | 49 | To schedule the minute from 00:00 - 00:01 the bits for the first hour would look like this: 50 | 51 | 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 52 | 53 | Which would be in hex: 54 | 55 | 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 56 | 57 | And for 00:00 - 00:02: 58 | 59 | 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 60 | 61 | Again in hex: 62 | 63 | C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 64 | 65 | Now 00:00 - 00:03: 66 | 67 | 1110 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 68 | 69 | In hex: 70 | 71 | E 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 | 73 | Then 00:00 - 00:04: 74 | 75 | 1111 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 76 | 77 | In hex: 78 | 79 | F 0 0 0 0 0 0 0 0 0 0 0 0 0 0 80 | 81 | The same for the next for bytes with 00:00 - 00:05: 82 | 83 | 1111 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 84 | 85 | In hex: 86 | 87 | F 8 0 0 0 0 0 0 0 0 0 0 0 0 0 88 | 89 | ... And so forth 90 | -------------------------------------------------------------------------------- /src/ediplug/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'stefan' 2 | 3 | from ediplug.smartplug import SmartPlug 4 | -------------------------------------------------------------------------------- /src/ediplug/smartplug.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2018 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | 26 | import requests as req 27 | import optparse as par 28 | import logging as log 29 | 30 | from xml.dom.minidom import getDOMImplementation 31 | from xml.dom.minidom import parseString 32 | from requests.auth import HTTPDigestAuth 33 | 34 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 35 | 36 | 37 | class SmartPlug(object): 38 | """ 39 | Simple class to access a "EDIMAX Smart Plug Switch SP1101W/SP2101W" 40 | 41 | Usage example when used as library: 42 | 43 | p = SmartPlug("172.16.100.75", ('admin', '1234')) 44 | 45 | # get device info 46 | print(p.info) 47 | 48 | # change state of plug 49 | p.state = "OFF" 50 | p.state = "ON" 51 | 52 | # query and print current state of plug 53 | print(p.state) 54 | 55 | # get power consumption (only SP2101W) 56 | print(p.power) 57 | 58 | # get current consumption (only SP2101W) 59 | print(p.current) 60 | 61 | # read and print complete week schedule from plug 62 | print(p.schedule.__str__()) 63 | 64 | # write schedule for on day to plug (Saturday, 11:15 - 11:45) 65 | p.schedule = {'state': u'ON', 'sched': [[[11, 15], [11, 45]]], 'day': 6} 66 | 67 | # write schedule for the whole week 68 | p.schedule = [ 69 | {'state': u'ON', 'sched': [[[0, 3], [0, 4]]], 'day': 0}, 70 | {'state': u'ON', 'sched': [[[0, 10], [0, 20]], [[10, 16], [11, 55]], 71 | [[15, 19], [15, 32]], [[21, 0], [23, 8]], [[23, 17], [23, 59]]], 'day': 1}, 72 | {'state': u'OFF', 'sched': [[[19, 59], [21, 1]]], 'day': 2}, 73 | {'state': u'OFF', 'sched': [[[20, 59], [21, 12]]], 'day': 3}, 74 | {'state': u'OFF', 'sched': [], 'day': 4}, 75 | {'state': u'OFF', 'sched': [[[0, 0], [0, 30]], [[11, 14], [14, 31]]], 'day': 5}, 76 | {'state': u'ON', 'sched': [[[1, 42], [2, 41]]], 'day': 6}] 77 | 78 | 79 | Usage example when used as command line utility: 80 | 81 | Get device info: 82 | 83 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -i 84 | 85 | turn plug on: 86 | 87 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -s ON 88 | 89 | turn plug off: 90 | 91 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -s OFF 92 | 93 | get plug state: 94 | 95 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -g 96 | 97 | get power consumption (only SP2101W) 98 | 99 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -w 100 | 101 | get current consumption (only SP2101W) 102 | 103 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -a 104 | 105 | get schedule of the whole week: 106 | 107 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -G 108 | 109 | get schedule of the whole week as python array: 110 | 111 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -P 112 | 113 | set schedule for one day: 114 | 115 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -S 116 | "{'state': u'ON', 'sched': [[[11, 0], [11, 45]]], 'day': 6}" 117 | 118 | set schedule for the whole week: 119 | 120 | python smartplug.py -H 172.16.100.75 -l admin -p 1234 -S "[ 121 | {'state': u'ON', 'sched': [[[1, 0], [1, 1]]], 'day': 0}, 122 | {'state': u'ON', 'sched': [[[2, 0], [2, 2]]], 'day': 1}, 123 | {'state': u'ON', 'sched': [[[3, 0], [3, 3]]], 'day': 2}, 124 | {'state': u'ON', 'sched': [[[4, 0], [4, 4]]], 'day': 3}, 125 | {'state': u'ON', 'sched': [[[5, 0], [5, 5]]], 'day': 4}, 126 | {'state': u'ON', 'sched': [[[6, 0], [6, 6]]], 'day': 5}, 127 | {'state': u'ON', 'sched': [[[7, 0], [7, 7]]], 'day': 6}, 128 | ]" 129 | """ 130 | 131 | def __init__(self, host, auth): 132 | """ 133 | Create a new SmartPlug instance identified by the given URL. 134 | 135 | :rtype: object 136 | :param host: The IP/hostname of the SmartPlug. E.g. '172.16.100.75' 137 | :param auth: User and password to authenticate with the plug. E.g. ('admin', '1234') 138 | """ 139 | object.__init__(self) 140 | 141 | self.url = "http://%s:10000/smartplug.cgi" % host 142 | self.auth = auth 143 | self.domi = getDOMImplementation() 144 | 145 | # Make a request to detect if Authentication type is Digest 146 | res = req.head(self.url) 147 | if res.headers['WWW-Authenticate'][0:6] == 'Digest': 148 | self.auth = HTTPDigestAuth(auth[0], auth[1]) 149 | 150 | self.log = log.getLogger("SmartPlug") 151 | 152 | def _xml_cmd_setget_state(self, cmdId, cmdStr): 153 | """ 154 | Create XML representation of a state command. 155 | 156 | :type self: object 157 | :type cmdId: str 158 | :type cmdStr: str 159 | :rtype: str 160 | :param cmdId: Use 'get' to request plug state, use 'setup' change plug state. 161 | :param cmdStr: Empty string for 'get', 'ON' or 'OFF' for 'setup' 162 | :return: XML representation of command 163 | """ 164 | 165 | assert (cmdId == "setup" and cmdStr in ["ON", "OFF"]) or (cmdId == "get" and cmdStr == "") 166 | 167 | doc = self.domi.createDocument(None, "SMARTPLUG", None) 168 | doc.documentElement.setAttribute("id", "edimax") 169 | 170 | cmd = doc.createElement("CMD") 171 | cmd.setAttribute("id", cmdId) 172 | state = doc.createElement("Device.System.Power.State") 173 | cmd.appendChild(state) 174 | state.appendChild(doc.createTextNode(cmdStr)) 175 | 176 | doc.documentElement.appendChild(cmd) 177 | 178 | xml = doc.toxml() 179 | self.log.debug("Request: %s" % xml) 180 | 181 | return xml 182 | 183 | def _xml_cmd_get_pc(self, what): 184 | """ 185 | Get power or current consumption (only SP2101W). 186 | 187 | :type self: object 188 | :type what: str 189 | :rtype: str 190 | :param what: What to retrieve: "NowPower" or "NowCurrent 191 | :return: XML representation of command 192 | """ 193 | 194 | assert what in ["NowPower", "NowCurrent"] 195 | 196 | doc = self.domi.createDocument(None, "SMARTPLUG", None) 197 | doc.documentElement.setAttribute("id", "edimax") 198 | 199 | cmd = doc.createElement("CMD") 200 | cmd.setAttribute("id", "get") 201 | pwr = doc.createElement("NOW_POWER") 202 | cmd.appendChild(pwr) 203 | state = doc.createElement("Device.System.Power.%s" % what) 204 | pwr.appendChild(state) 205 | 206 | doc.documentElement.appendChild(cmd) 207 | 208 | xml = doc.toxml() 209 | self.log.debug("Request: %s" % xml) 210 | 211 | return xml 212 | 213 | def _xml_cmd_get_info(self): 214 | """ 215 | Create XML representation of a command to query some information 216 | 217 | :type self: object 218 | :rtype: str 219 | :return: XML representation of command 220 | """ 221 | 222 | doc = self.domi.createDocument(None, "SMARTPLUG", None) 223 | doc.documentElement.setAttribute("id", "edimax") 224 | 225 | cmd = doc.createElement("CMD") 226 | cmd.setAttribute("id", "get") 227 | si = doc.createElement("SYSTEM_INFO") 228 | cmd.appendChild(si) 229 | doc.documentElement.appendChild(cmd) 230 | 231 | xml = doc.toxml() 232 | self.log.debug("Request: %s" % xml) 233 | 234 | return xml 235 | 236 | def _xml_cmd_get_sched(self): 237 | """ 238 | Create XML representation of a command to query schedule of whole week from plug. 239 | 240 | :type self: object 241 | :rtype: str 242 | :return: XML representation of command 243 | """ 244 | 245 | doc = self.domi.createDocument(None, "SMARTPLUG", None) 246 | doc.documentElement.setAttribute("id", "edimax") 247 | 248 | cmd = doc.createElement("CMD") 249 | cmd.setAttribute("id", "get") 250 | sched = doc.createElement("SCHEDULE") 251 | cmd.appendChild(sched) 252 | doc.createElement("Device.System.Power.State") 253 | 254 | doc.documentElement.appendChild(cmd) 255 | 256 | xml = doc.toxml() 257 | self.log.debug("Request: %s" % xml) 258 | 259 | return xml 260 | 261 | def _xml_cmd_set_sched(self, sched_days): 262 | """ 263 | Create XML representation of a command to set scheduling for one day or whole week. 264 | 265 | :type self: object 266 | :type sched_days: list 267 | :rtype: str 268 | :param sched_day: Single day or whole week 269 | :return: XML representation of command 270 | """ 271 | 272 | doc = self.domi.createDocument(None, "SMARTPLUG", None) 273 | doc.documentElement.setAttribute("id", "edimax") 274 | 275 | cmd = doc.createElement("CMD") 276 | cmd.setAttribute("id", "setup") 277 | sched = doc.createElement("SCHEDULE") 278 | cmd.appendChild(sched) 279 | 280 | if isinstance(sched_days, list): 281 | # more then one day 282 | 283 | for one_sched_day in sched_days: 284 | 285 | dev_sched = doc.createElement("Device.System.Power.Schedule.%d" % one_sched_day["day"]) 286 | dev_sched.appendChild(doc.createTextNode(self._render_schedule(one_sched_day["sched"]))) 287 | dev_sched.attributes["value"] = one_sched_day["state"] 288 | 289 | sched.appendChild(dev_sched) 290 | 291 | else: 292 | # one day 293 | dev_sched = doc.createElement("Device.System.Power.Schedule.%d" % sched_days["day"]) 294 | dev_sched.appendChild(doc.createTextNode(self._render_schedule(sched_days["sched"]))) 295 | dev_sched.attributes["value"] = sched_days["state"] 296 | 297 | sched.appendChild(dev_sched) 298 | 299 | doc.documentElement.appendChild(cmd) 300 | 301 | xml = doc.toxml() 302 | self.log.debug("Request: %s" % xml) 303 | 304 | return xml 305 | 306 | def _post_xml(self, xml): 307 | """ 308 | Post XML command as multipart file to SmartPlug, parse XML response. 309 | 310 | :type self: object 311 | :type xml: str 312 | :rtype: str 313 | :param xml: XML representation of command (as generated by _xml_cmd) 314 | :return: 'OK' on success, 'FAILED' otherwise 315 | """ 316 | 317 | files = {'file': xml} 318 | 319 | res = req.post(self.url, auth=self.auth, files=files) 320 | 321 | self.log.debug("Status code: %d" % res.status_code) 322 | self.log.debug("Response: %s" % res.text) 323 | 324 | if res.status_code == req.codes.ok: 325 | dom = parseString(res.text) 326 | 327 | try: 328 | val = dom.getElementsByTagName("CMD")[0].firstChild.nodeValue 329 | 330 | if val is None: 331 | val = dom.getElementsByTagName("CMD")[0].getElementsByTagName("Device.System.Power.State")[0].\ 332 | firstChild.nodeValue 333 | 334 | return val 335 | 336 | except Exception as e: 337 | 338 | print(e.__str__()) 339 | 340 | return None 341 | 342 | def _post_xml_dom(self, xml): 343 | """ 344 | Post XML command as multipart file to SmartPlug, return response as raw dom. 345 | 346 | :type self: object 347 | :type xml: str 348 | :rtype: object 349 | :param xml: XML representation of command (as generated by _xml_cmd) 350 | :return: dom representation of XML response 351 | """ 352 | 353 | files = {'file': xml} 354 | 355 | res = req.post(self.url, auth=self.auth, files=files) 356 | 357 | self.log.debug("Status code: %d" % res.status_code) 358 | self.log.debug("Response: %s" % res.text) 359 | 360 | if res.status_code == req.codes.ok: 361 | return parseString(res.text) 362 | 363 | return None 364 | 365 | @property 366 | def info(self): 367 | """ 368 | Get device info (vendor, model, version, mac and system name (if available)). 369 | 370 | :type self: object 371 | :rtype: dictonary 372 | :return: dictonary with the following keys: vendor, model, version, mac, name 373 | """ 374 | 375 | dom = self._post_xml_dom(self._xml_cmd_get_info()) 376 | 377 | vendor = dom.getElementsByTagName("Run.Cus")[0].firstChild.nodeValue 378 | model = dom.getElementsByTagName("Run.Model")[0].firstChild.nodeValue 379 | version = dom.getElementsByTagName("Run.FW.Version")[0].firstChild.nodeValue 380 | mac = dom.getElementsByTagName("Run.LAN.Client.MAC.Address")[0].firstChild.nodeValue 381 | 382 | inf = {"vendor":vendor, "model":model, "version":version, "mac":mac} 383 | 384 | # not all plugs/fw versions seem to return the system name ... 385 | try: 386 | inf["name"] = dom.getElementsByTagName("Device.System.Name")[0].firstChild.nodeValue 387 | except IndexError: 388 | pass 389 | 390 | return inf 391 | 392 | @property 393 | def state(self): 394 | """ 395 | Get the current state of the SmartPlug. 396 | 397 | :type self: object 398 | :rtype: str 399 | :return: 'ON' or 'OFF' 400 | """ 401 | 402 | res = self._post_xml(self._xml_cmd_setget_state("get", "")) 403 | 404 | if res != "ON" and res != "OFF": 405 | raise Exception("Failed to communicate with SmartPlug") 406 | 407 | return res 408 | 409 | @state.setter 410 | def state(self, value): 411 | """ 412 | Set the state of the SmartPlug 413 | 414 | :type self: object 415 | :type value: str 416 | :param value: 'ON', 'on', 'OFF' or 'off' 417 | """ 418 | 419 | if value == "ON" or value == "on": 420 | res = self._post_xml(self._xml_cmd_setget_state("setup", "ON")) 421 | else: 422 | res = self._post_xml(self._xml_cmd_setget_state("setup", "OFF")) 423 | 424 | if res != "OK": 425 | raise Exception("Failed to communicate with SmartPlug") 426 | 427 | @property 428 | def power(self): 429 | """ 430 | Get the power consumption of the SmartPlug (only SP2101W). 431 | 432 | :type self: object 433 | :rtype: tuple (str, float) 434 | :return: power consumption in W 435 | """ 436 | 437 | dom = self._post_xml_dom(self._xml_cmd_get_pc("NowPower")) 438 | 439 | try: 440 | power = dom.getElementsByTagName("Device.System.Power.NowPower")[0].firstChild.nodeValue 441 | except: 442 | raise Exception("Failed to communicate with SmartPlug") 443 | 444 | return power 445 | 446 | @property 447 | def current(self): 448 | """ 449 | Get the current consumption of the SmartPlug (only SP2101W). 450 | 451 | :type self: object 452 | :rtype: tuple (str, float) 453 | :return: current consumption in A 454 | """ 455 | 456 | dom = self._post_xml_dom(self._xml_cmd_get_pc("NowCurrent")) 457 | 458 | try: 459 | current = dom.getElementsByTagName("Device.System.Power.NowCurrent")[0].firstChild.nodeValue 460 | except: 461 | raise Exception("Failed to communicate with SmartPlug") 462 | 463 | return current 464 | 465 | def _parse_schedule(self, sched): 466 | """ 467 | Parse the plugs internal scheduling format string to python array 468 | 469 | :type self: object 470 | :type sched: str 471 | :rtype: list 472 | :param sched: scheduling string (of one day) as returned by plug 473 | :return: Python array with scheduling: [[[start_hh:start_mm],[end_hh:end_mm]], ... ] 474 | """ 475 | 476 | sched_unpacked = [0] * 60 * 24 477 | hours = [] 478 | 479 | idx_sched = 0 480 | 481 | # first, unpack the packed schedule from the plug 482 | for packed in sched: 483 | 484 | int_packed = int(packed, 16) 485 | 486 | sched_unpacked[idx_sched+0] = (int_packed >> 3) & 1 487 | sched_unpacked[idx_sched+1] = (int_packed >> 2) & 1 488 | sched_unpacked[idx_sched+2] = (int_packed >> 1) & 1 489 | sched_unpacked[idx_sched+3] = (int_packed >> 0) & 1 490 | 491 | idx_sched += 4 492 | 493 | idx_hours = 0 494 | 495 | hour = 0 496 | min = 0 497 | 498 | found_range = False 499 | 500 | # second build time array from unpacked schedule 501 | for m in sched_unpacked: 502 | 503 | if m == 1 and not found_range: 504 | found_range = True 505 | hours.append([[hour, min], [23, 59]]) 506 | 507 | elif m == 0 and found_range: 508 | found_range = False 509 | hours[idx_hours][1][0] = hour 510 | hours[idx_hours][1][1] = min 511 | idx_hours += 1 512 | 513 | min += 1 514 | 515 | if min > 59: 516 | min = 0 517 | hour += 1 518 | 519 | return hours 520 | 521 | def _render_schedule(self, hours): 522 | """ 523 | Render Python scheduling array back to plugs internal format 524 | 525 | :type self: object 526 | :type hours: list 527 | :rtype: str 528 | :param hours: Python array with scheduling hours: [[[start_hh:start_mm],[end_hh:end_mm]], ... ] 529 | :return: scheduling string (of one day) as needed by plug 530 | """ 531 | 532 | sched = [0] * 60 * 24 533 | sched_str = '' 534 | 535 | # first, set every minute we found a schedule to 1 in the sched array 536 | for times in hours: 537 | 538 | idx_start = times[0][0] * 60 + times[0][1] 539 | idx_end = times[1][0] * 60 + times[1][1] 540 | 541 | if idx_end < idx_start: 542 | idx_end = 60 * 24 543 | 544 | for i in range(idx_start, idx_end): 545 | sched[i] = 1 546 | 547 | # second, pack the minute array from above into the plug format and make a string out of it 548 | for i in range(0, 60 * 24, 4): 549 | packed = (sched[i] << 3) + (sched[i+1] << 2) + (sched[i+2] << 1) + (sched[i+3] << 0) 550 | sched_str += "%X" % packed 551 | 552 | return sched_str 553 | 554 | @property 555 | def schedule(self): 556 | """ 557 | Get scheduling for all days of week from plug as python list. 558 | Note: it looks like the plug only is able to return a whole week. 559 | 560 | :type self: object 561 | :rtype: list 562 | :return: List with scheduling for each day of week: 563 | 564 | [ 565 | {'state': u'ON|OFF', 'sched': [[[hh, mm], [hh, mm]], ...], 'day': 0..6}, 566 | ... 567 | ] 568 | """ 569 | 570 | sched = [] 571 | 572 | dom = self._post_xml_dom(self._xml_cmd_get_sched()) 573 | 574 | if dom is None: 575 | return sched 576 | 577 | try: 578 | 579 | dom_sched = dom.getElementsByTagName("CMD")[0].getElementsByTagName("SCHEDULE")[0] 580 | 581 | for i in range(0, 7): 582 | 583 | sched.append( 584 | {"day": i, 585 | "state": dom_sched.getElementsByTagName("Device.System.Power.Schedule.%d" % i)[0].attributes[ 586 | "value"]. 587 | firstChild.nodeValue, 588 | "sched": self._parse_schedule( 589 | dom_sched.getElementsByTagName("Device.System.Power.Schedule.%d" % i)[0]. 590 | firstChild.nodeValue)}) 591 | 592 | except Exception as e: 593 | 594 | print(e.__str__()) 595 | 596 | return sched 597 | 598 | @schedule.setter 599 | def schedule(self, sched): 600 | """ 601 | Set scheduling for ony day of week or for whole week on the plug. 602 | Note: it seams not to be possible to schedule anything else then one day or a whole week. 603 | 604 | :type self: object 605 | :type sched: list 606 | :rtype: str 607 | :param sched: Array with scheduling hours for ons day: 608 | 609 | {'day': 0..6, 'state': 'ON' | 'OFF', [[start_hh:start_mm],[end_hh:end_mm]], ... ]} 610 | 611 | Or whole week: 612 | 613 | [{'day': 0..6, 'state': 'ON' | 'OFF', [[start_hh:start_mm],[end_hh:end_mm]], ... ]}, ...] 614 | 615 | :return: 'OK' (or exception on error) 616 | """ 617 | 618 | res = self._post_xml(self._xml_cmd_set_sched(sched)) 619 | 620 | if res != "OK": 621 | raise Exception("Failed to communicate with SmartPlug") 622 | 623 | return res 624 | 625 | if __name__ == "__main__": 626 | 627 | usage = "%prog [options]" 628 | 629 | parser = par.OptionParser(usage) 630 | 631 | parser.add_option("-v", "--verbose", action="store_true", help="Print debug information") 632 | 633 | parser.add_option("-H", "--host", default="172.16.100.75", help="Base URL of the SmartPlug") 634 | parser.add_option("-l", "--login", default="admin", help="Login user to authenticate with SmartPlug") 635 | parser.add_option("-p", "--password", default="1234", help="Password to authenticate with SmartPlug") 636 | 637 | parser.add_option("-i", "--info", action="store_true", help="Get plug information") 638 | parser.add_option("-g", "--get", action="store_true", help="Get state of plug") 639 | parser.add_option("-s", "--set", help="Set state of plug: ON or OFF") 640 | 641 | parser.add_option("-w", "--power", action="store_true", help="Get plug power consumption (only SP2101W)") 642 | parser.add_option("-a", "--current", action="store_true", help="Get plug current consumption (only SP2101W)") 643 | 644 | parser.add_option("-G", "--getsched", action="store_true", help="Get schedule from Plug") 645 | parser.add_option("-P", "--getschedpy", action="store_true", help="Get schedule from Plug as Python list") 646 | parser.add_option("-S", "--setsched", help="Set schedule of Plug") 647 | 648 | (options, args) = parser.parse_args() 649 | 650 | # this turns on debugging 651 | level = log.ERROR 652 | 653 | if options.verbose: 654 | level = log.DEBUG 655 | 656 | log.basicConfig(level=level, format='%(asctime)s - %(levelname) 8s [%(module) 15s] - %(message)s') 657 | 658 | p = SmartPlug(options.host, (options.login, options.password)) 659 | 660 | if options.info: 661 | 662 | print("Plug info:") 663 | for i in sorted(p.info.items()): 664 | print("- %s: %s" % i) 665 | 666 | if options.get: 667 | 668 | print(p.state) 669 | 670 | elif options.set: 671 | 672 | p.state = options.set 673 | 674 | if options.power: 675 | 676 | print("%s W" % p.power) 677 | 678 | if options.current: 679 | 680 | print("%s A" % p.current) 681 | 682 | elif options.getsched: 683 | 684 | days = {0: "Sunday", 1: "Monday", 2: "Tuesday", 3: "Wednesday", 685 | 4: "Thursday", 5: "Friday", 6: "Saturday"} 686 | 687 | for day in p.schedule: 688 | 689 | if len(day["sched"]) > 0: 690 | print("Schedules for: %s (%s)" % (days[day["day"]], day["state"])) 691 | 692 | for sched in day["sched"]: 693 | print(" * %02d:%02d - %02d:%02d" % (sched[0][0], sched[0][1], sched[1][0], sched[1][1])) 694 | 695 | elif options.getschedpy: 696 | 697 | print(p.schedule.__str__()) 698 | 699 | elif options.setsched: 700 | 701 | try: 702 | 703 | sched = eval(options.setsched) 704 | p.schedule = sched 705 | 706 | except Exception as e: 707 | 708 | print("Wrong input format: %s" % e.__str__()) 709 | exit(-1) 710 | -------------------------------------------------------------------------------- /src/examples/sample_01.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Very simple example showing how to use the SmartPlug API to turn the plug ON and OFF. 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # create plug object for plug with given IP, login admin and password 1234 36 | p = SmartPlug("192.168.1.117", ('admin', '1234')) 37 | 38 | # change state of plug to ON 39 | p.state = "ON" 40 | print("Plug is now: ", p.state) 41 | 42 | # change state of plug to OFF 43 | p.state = "OFF" 44 | print("Plug is now: ", p.state) -------------------------------------------------------------------------------- /src/examples/sample_02.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Very simple example showing how to use the SmartPlug API query week schedule. 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # create plug object for plug with given IP, login admin and password 1234 36 | p = SmartPlug("192.168.1.117", ('admin', '1234')) 37 | 38 | # helper to map numerical days to named days 39 | days = { 0 : "Sunday", 1 : "Monday", 2 : "Tuesday", 3 : "Wednesday", 40 | 4 : "Thursday", 5 : "Friday", 6 : "Saturday"} 41 | 42 | # request schedule from plug, display information for each day 43 | for day in p.schedule: 44 | 45 | # only if this day has a schedule, print it 46 | if len(day["sched"]) > 0: 47 | 48 | # print day information 49 | print("Schedules for: %s (%s)" % (days[day["day"]], day["state"])) 50 | 51 | # print scheduled hours 52 | for sched in day["sched"]: 53 | print(" * %02d:%02d - %02d:%02d" % (sched[0][0], sched[0][1], sched[1][0], sched[1][1])) -------------------------------------------------------------------------------- /src/examples/sample_03.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Very simple example showing how to use the SmartPlug API to set the schedule of one day. 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # create plug object for plug with given IP, login admin and password 1234 36 | p = SmartPlug("192.168.1.117", ('admin', '1234')) 37 | 38 | # write schedule for one day to plug (Saturday, 11:15 - 11:45) 39 | p.schedule = {'state': u'ON', 'sched': [[[11, 15], [11, 45]]], 'day': 6} -------------------------------------------------------------------------------- /src/examples/sample_04.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Very simple example showing how to use the SmartPlug API to set the schedule of one week. 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # create plug object for plug with given IP, login admin and password 1234 36 | p = SmartPlug("192.168.1.117", ('admin', '1234')) 37 | 38 | # write schedule for one week 39 | p.schedule = [ 40 | {'state': u'ON', 'sched': [[[1, 0], [1, 30]]], 'day': 0}, 41 | {'state': u'ON', 'sched': [[[2, 0], [2, 30]]], 'day': 1}, 42 | {'state': u'ON', 'sched': [[[3, 0], [3, 30]]], 'day': 2}, 43 | {'state': u'ON', 'sched': [[[4, 0], [4, 30]]], 'day': 3}, 44 | {'state': u'ON', 'sched': [[[5, 0], [5, 30]]], 'day': 4}, 45 | {'state': u'ON', 'sched': [[[6, 0], [6, 30]]], 'day': 5}, 46 | {'state': u'ON', 'sched': [[[7, 0], [7, 30]]], 'day': 6}, 47 | ] -------------------------------------------------------------------------------- /src/examples/sample_05.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Very simple example showing how to use the SmartPlug API to read some device info. 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # create plug object for plug with given IP, login admin and password 1234 36 | p = SmartPlug("192.168.1.117", ("admin", "1234")) 37 | 38 | # device info is returned as dictonary 39 | for i in sorted(p.info.items()): 40 | print("%s: %s" % i) 41 | 42 | -------------------------------------------------------------------------------- /src/examples/sample_06.py: -------------------------------------------------------------------------------- 1 | ## 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2018 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | ## 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | This example shows how to connect the plug to Blynk (http://www.blynk.cc/). 29 | 30 | The following is needed to make this example run: 31 | 32 | - BlynkLib for Python installed: 33 | - e.g.: pip install blynk-library-python 34 | - for details go to: https://github.com/vshymanskyy/blynk-library-python 35 | - Blynk App for your smart phone: 36 | - Android: https://play.google.com/store/apps/details?id=cc.blynk 37 | - iOS: https://itunes.apple.com/us/app/blynk-control-arduino-raspberry/id808760481?ls=1&mt=8 38 | 39 | Next, in the Blynk App on the phone, create a new project (e.g. for generic board). 40 | A token will be send to you by email. Copy the token into 'BLYNK_AUTH' below. 41 | Back in the Blynk App, add a button widget, and assign virtual PIN V0 to it. 42 | 43 | In the code below also fill in the correct values for 'PLUG_IP', 'PLUG_LOGIN' and 'PLUG_PASSWORD' 44 | 45 | Now fire up this example, and you should see it go online in the phone app. Switch the 46 | plug with the button in the app. 47 | 48 | An other nice thing is, that you are now also able to reach your plug via the 49 | Blynk REST API through the WWW doing the following: 50 | 51 | - get the IP of your countries Blynk could server e.g. with ping: 52 | 53 | ping cloud.blynk.cc 54 | 55 | E.g. for europe this returns: 139.59.206.133 56 | 57 | - then make a PUT request with the following URL to turn the plug ON, e.g. using CURL: 58 | 59 | curl -H "Content-Type: application/json" -X PUT -d '["1"]' http://139.59.206.133/BLYNK_TOKEN/pin/V0 60 | 61 | and to turn it OFF: 62 | 63 | curl -H "Content-Type: application/json" -X PUT -d '["0"]' http://139.59.206.133/BLYNK_TOKEN/pin/V0 64 | 65 | This also works nicely with IFTTT when using 'webhook' as action and configuring it like this: 66 | 67 | - URL : http://139.59.206.133/BLYNK_TOKEN/pin/V0 68 | - Method : PUT 69 | - Content-Type: application/json 70 | - Body : ["1"] for ON or ["0"] for OFF 71 | 72 | """ 73 | 74 | import BlynkLib 75 | import ediplug 76 | 77 | BLYNK_AUTH = 'YourAuthToken' 78 | 79 | PLUG_IP = '192.168.1.117' 80 | PLUG_LOGIN = 'admin' 81 | PLUG_PASSWORD = '1234' 82 | 83 | blynk = BlynkLib.Blynk(BLYNK_AUTH) 84 | plug = ediplug.SmartPlug(PLUG_IP, (PLUG_LOGIN, PLUG_PASSWORD)) 85 | 86 | 87 | @blynk.VIRTUAL_WRITE(0) 88 | def ediplug_write_handler(value): 89 | """ 90 | Called by Blynk whenever the button state changed. 91 | 92 | :param value: 1 - ON, 0 - OFF 93 | """ 94 | global plug 95 | 96 | if value == '1': 97 | plug.state = 'ON' 98 | print("Turning plug ON") 99 | else: 100 | plug.state = 'OFF' 101 | print("Turning plug OFF") 102 | 103 | 104 | def blynk_connected(): 105 | """ 106 | On startup sync state from Blynk server to plug. 107 | """ 108 | global blynk 109 | 110 | print("Updating all values from the server...") 111 | blynk.sync_all() 112 | 113 | 114 | blynk.on_connect(blynk_connected) 115 | blynk.run() 116 | -------------------------------------------------------------------------------- /src/utest/parse_schedule.py: -------------------------------------------------------------------------------- 1 | # # 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # # 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Test case for parsing schedule into plug-format 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # for unit test 36 | import unittest 37 | 38 | 39 | class TestParseSchedule(unittest.TestCase): 40 | 41 | def setUp(self): 42 | self.sp = SmartPlug(None, None) 43 | 44 | def test_parse_schedule_empty(self): 45 | rs = self.sp._parse_schedule('000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 46 | 47 | print("parsed result ist: ", rs) 48 | 49 | self.assertEqual(rs, 50 | [], 51 | 'Wrong values') 52 | 53 | def test_parse_schedule_edges(self): 54 | 55 | rs = self.sp._parse_schedule('FFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFF') 56 | 57 | print("parsed result ist: ", rs) 58 | 59 | self.assertEqual(rs, 60 | [[[0, 0], [1, 0]], [[23, 00], [23, 59]]], 61 | 'Wrong values') 62 | 63 | def test_parse_schedule_short_interval_8(self): 64 | rs = self.sp._parse_schedule('800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 65 | 66 | print("parsed result ist: ", rs) 67 | 68 | self.assertEqual(rs, 69 | [[[0, 0], [0, 1]]], 70 | 'Wrong values') 71 | 72 | def test_parse_schedule_short_interval_C(self): 73 | rs = self.sp._parse_schedule('C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 74 | 75 | print("parsed result ist: ", rs) 76 | 77 | self.assertEqual(rs, 78 | [[[0, 0], [0, 2]]], 79 | 'Wrong values') 80 | 81 | def test_parse_schedule_short_interval_E(self): 82 | rs = self.sp._parse_schedule('E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 83 | 84 | print("parsed result ist: ", rs) 85 | 86 | self.assertEqual(rs, 87 | [[[0, 0], [0, 3]]], 88 | 'Wrong values') 89 | 90 | def test_parse_schedule_short_interval_F(self): 91 | rs = self.sp._parse_schedule('F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 92 | 93 | print("parsed result ist: ", rs) 94 | 95 | self.assertEqual(rs, 96 | [[[0, 0], [0, 4]]], 97 | 'Wrong values') 98 | 99 | def test_parse_schedule_short_interval_7(self): 100 | rs = self.sp._parse_schedule('7FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 101 | 102 | print("parsed result ist: ", rs) 103 | 104 | self.assertEqual(rs, 105 | [[[0, 1], [1, 0]]], 106 | 'Wrong values') 107 | 108 | def test_parse_schedule_short_interval_3(self): 109 | rs = self.sp._parse_schedule('3FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 110 | 111 | print("parsed result ist: ", rs) 112 | 113 | self.assertEqual(rs, 114 | [[[0, 2], [1, 0]]], 115 | 'Wrong values') 116 | 117 | def test_parse_schedule_short_interval_1(self): 118 | rs = self.sp._parse_schedule('1FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 119 | 120 | print("parsed result ist: ", rs) 121 | 122 | self.assertEqual(rs, 123 | [[[0, 3], [1, 0]]], 124 | 'Wrong values') 125 | 126 | def test_parse_schedule_short_interval_4(self): 127 | rs = self.sp._parse_schedule('400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 128 | 129 | print("parsed result ist: ", rs) 130 | 131 | self.assertEqual(rs, 132 | [[[0, 1], [0, 2]]], 133 | 'Wrong values') 134 | 135 | def test_parse_schedule_short_interval_6(self): 136 | rs = self.sp._parse_schedule('600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 137 | 138 | print("parsed result ist: ", rs) 139 | 140 | self.assertEqual(rs, 141 | [[[0, 1], [0, 3]]], 142 | 'Wrong values') 143 | 144 | def test_parse_schedule_short_interval_2(self): 145 | rs = self.sp._parse_schedule('200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 146 | 147 | print("parsed result ist: ", rs) 148 | 149 | self.assertEqual(rs, 150 | [[[0, 2], [0, 3]]], 151 | 'Wrong values') 152 | 153 | def test_parse_schedule_short_interval_seq(self): 154 | rs = self.sp._parse_schedule('0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000') 155 | 156 | print("parsed result ist: ", rs) 157 | 158 | self.assertEqual(rs, 159 | [[[13, 2], [13, 3]], [[13, 4], [13, 5]], [[13, 6], [13, 7]]], 160 | 'Wrong values') 161 | 162 | if __name__ == '__main__': 163 | suite = unittest.TestLoader().loadTestsFromTestCase(TestParseSchedule) 164 | unittest.TextTestRunner(verbosity=2).run(suite) -------------------------------------------------------------------------------- /src/utest/render_schedule.py: -------------------------------------------------------------------------------- 1 | # # 2 | # The MIT License (MIT) 3 | # 4 | # Copyright (c) 2014 Stefan Wendler 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in 14 | # all copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | # THE SOFTWARE. 23 | # # 24 | 25 | __author__ = 'Stefan Wendler, sw@kaltpost.de' 26 | 27 | """ 28 | Test case for rendering schedule into plug-format 29 | 30 | """ 31 | 32 | # import plug API 33 | from ediplug.smartplug import SmartPlug 34 | 35 | # for unit test 36 | import unittest 37 | 38 | 39 | class TestRenderSchedule(unittest.TestCase): 40 | 41 | def setUp(self): 42 | self.sp = SmartPlug(None, None) 43 | 44 | def test_render_schedule_empty(self): 45 | rs = self.sp._render_schedule([]) 46 | 47 | print("rendered result ist: ", rs) 48 | 49 | self.assertEqual(rs, 50 | '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 51 | 'Wrong values') 52 | 53 | def test_render_schedule_edges(self): 54 | 55 | rs = self.sp._render_schedule([[[0, 0], [1, 0]], [[23, 00], [0, 0]]]) 56 | 57 | print("rendered result ist: ", rs) 58 | 59 | self.assertEqual(rs, 'FFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFF', 60 | 'Wrong values') 61 | 62 | def test_render_schedule_short_interval_8(self): 63 | rs = self.sp._render_schedule([[[0, 0], [0, 1]]]) 64 | 65 | print("rendered result ist: ", rs) 66 | 67 | self.assertEqual(rs, 68 | '800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 69 | 'Wrong values') 70 | 71 | def test_render_schedule_short_interval_C(self): 72 | rs = self.sp._render_schedule([[[0, 0], [0, 2]]]) 73 | 74 | print("rendered result ist: ", rs) 75 | 76 | self.assertEqual(rs, 77 | 'C00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 78 | 'Wrong values') 79 | 80 | def test_render_schedule_short_interval_E(self): 81 | rs = self.sp._render_schedule([[[0, 0], [0, 3]]]) 82 | 83 | print("rendered result ist: ", rs) 84 | 85 | self.assertEqual(rs, 86 | 'E00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 87 | 'Wrong values') 88 | 89 | def test_render_schedule_short_interval_F(self): 90 | rs = self.sp._render_schedule([[[0, 0], [0, 4]]]) 91 | 92 | print("rendered result ist: ", rs) 93 | 94 | self.assertEqual(rs, 95 | 'F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 96 | 'Wrong values') 97 | 98 | def test_render_schedule_short_interval_7(self): 99 | rs = self.sp._render_schedule([[[0, 1], [1, 0]]]) 100 | 101 | print("rendered result ist: ", rs) 102 | 103 | self.assertEqual(rs, 104 | '7FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 105 | 'Wrong values') 106 | 107 | def test_render_schedule_short_interval_3(self): 108 | rs = self.sp._render_schedule([[[0, 2], [1, 0]]]) 109 | 110 | print("rendered result ist: ", rs) 111 | 112 | self.assertEqual(rs, 113 | '3FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 114 | 'Wrong values') 115 | 116 | def test_render_schedule_short_interval_1(self): 117 | rs = self.sp._render_schedule([[[0, 3], [1, 0]]]) 118 | 119 | print("rendered result ist: ", rs) 120 | 121 | self.assertEqual(rs, 122 | '1FFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 123 | 'Wrong values') 124 | 125 | def test_render_schedule_short_interval_4(self): 126 | rs = self.sp._render_schedule([[[0, 1], [0, 2]]]) 127 | 128 | print("rendered result ist: ", rs) 129 | 130 | self.assertEqual(rs, 131 | '400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 132 | 'Wrong values') 133 | 134 | def test_render_schedule_short_interval_6(self): 135 | rs = self.sp._render_schedule([[[0, 1], [0, 3]]]) 136 | 137 | print("rendered result ist: ", rs) 138 | 139 | self.assertEqual(rs, 140 | '600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 141 | 'Wrong values') 142 | 143 | def test_render_schedule_short_interval_2(self): 144 | rs = self.sp._render_schedule([[[0, 2], [0, 3]]]) 145 | 146 | print("rendered result ist: ", rs) 147 | 148 | self.assertEqual(rs, 149 | '200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 150 | 'Wrong values') 151 | 152 | def test_render_schedule_short_interval_seq(self): 153 | rs = self.sp._render_schedule([[[13, 2], [13, 3]], [[13, 4], [13, 5]], [[13, 6], [13, 7]]]) 154 | 155 | print("rendered result ist: ", rs) 156 | 157 | self.assertEqual(rs, 158 | '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002A0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 159 | 'Wrong values') 160 | 161 | if __name__ == '__main__': 162 | suite = unittest.TestLoader().loadTestsFromTestCase(TestRenderSchedule) 163 | unittest.TextTestRunner(verbosity=2).run(suite) --------------------------------------------------------------------------------