forked from kizniche/Mycodo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathon_off_kasa_kp303.py
288 lines (249 loc) · 10.2 KB
/
on_off_kasa_kp303.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# coding=utf-8
#
# on_off_kp303.py - Output for KP303
#
import asyncio
import threading
import time
from flask_babel import lazy_gettext
from mycodo.config_translations import TRANSLATIONS
from mycodo.databases.models import OutputChannel
from mycodo.outputs.base_output import AbstractOutput
from mycodo.utils.constraints_pass import constraints_pass_positive_value
from mycodo.utils.database import db_retrieve_table_daemon
# Measurements
measurements_dict = {
key: {
'measurement': 'duration_time',
'unit': 's'
}
for key in range(3)
}
channels_dict = {
key: {
'types': ['on_off'],
'name': f'Outlet {key + 1}',
'measurements': [key]
}
for key in range(3)
}
# Output information
OUTPUT_INFORMATION = {
'output_name_unique': 'kp303',
'output_name': "{}: Kasa KP303 3-Outlet WiFi Power Strip (old library, deprecated)".format(lazy_gettext('On/Off')),
'output_manufacturer': 'TP-Link',
'input_library': 'python-kasa',
'measurements_dict': measurements_dict,
'channels_dict': channels_dict,
'output_types': ['on_off'],
'url_manufacturer': 'https://www.tp-link.com/au/home-networking/smart-plug/kp303/',
'message': 'This output controls the 3 outlets of the Kasa KP303 Smart WiFi Power Strip. This module uses an outdated python library and is deprecated. Do not use it. You will break the current Kasa modules if you do not delete this deprecated Output.',
'options_enabled': [
'button_on',
'button_send_duration'
],
'options_disabled': ['interface'],
'dependencies_module': [
# Do not update past 0.4.0.dev4, 0.4.0.dev5 and above breaks this module's functionality
('pip-pypi', 'kasa', 'python-kasa==0.4.0.dev4')
],
'interfaces': ['IP'],
'custom_options': [
{
'id': 'plug_address',
'type': 'text',
'default_value': '192.168.0.50',
'required': True,
'name': TRANSLATIONS['host']['title'],
'phrase': TRANSLATIONS['host']['phrase']
},
{
'id': 'status_update_period',
'type': 'integer',
'default_value': 60,
'constraints_pass': constraints_pass_positive_value,
'required': True,
'name': 'Status Update (Seconds)',
'phrase': 'The period between checking if connected and output states.'
}
],
'custom_channel_options': [
{
'id': 'name',
'type': 'text',
'default_value': 'Outlet Name',
'required': True,
'name': TRANSLATIONS['name']['title'],
'phrase': TRANSLATIONS['name']['phrase']
},
{
'id': 'state_startup',
'type': 'select',
'default_value': 0,
'options_select': [
(-1, 'Do Nothing'),
(0, 'Off'),
(1, 'On')
],
'name': lazy_gettext('Startup State'),
'phrase': 'Set the state when Mycodo starts'
},
{
'id': 'state_shutdown',
'type': 'select',
'default_value': 0,
'options_select': [
(-1, 'Do Nothing'),
(0, 'Off'),
(1, 'On')
],
'name': lazy_gettext('Shutdown State'),
'phrase': 'Set the state when Mycodo shuts down'
},
{
'id': 'trigger_functions_startup',
'type': 'bool',
'default_value': False,
'name': lazy_gettext('Trigger Functions at Startup'),
'phrase': 'Whether to trigger functions when the output switches at startup'
},
{
'id': 'command_force',
'type': 'bool',
'default_value': False,
'name': lazy_gettext('Force Command'),
'phrase': 'Always send the command if instructed, regardless of the current state'
},
{
'id': 'amps',
'type': 'float',
'default_value': 0.0,
'required': True,
'name': "{} ({})".format(lazy_gettext('Current'), lazy_gettext('Amps')),
'phrase': 'The current draw of the device being controlled'
}
]
}
class OutputModule(AbstractOutput):
"""An output support class that operates an output."""
def __init__(self, output, testing=False):
super().__init__(output, testing=testing, name=__name__)
self.strip = None
self.outlet_switching = False
self.status_thread = None
self.outlet_status_checking = False
self.timer_status_check = time.time()
self.first_connect = True
self.plug_address = None
self.status_update_period = None
self.setup_custom_options(
OUTPUT_INFORMATION['custom_options'], output)
output_channels = db_retrieve_table_daemon(
OutputChannel).filter(OutputChannel.output_id == self.output.unique_id).all()
self.options_channels = self.setup_custom_channel_options_json(
OUTPUT_INFORMATION['custom_channel_options'], output_channels)
def initialize(self):
self.setup_output_variables(OUTPUT_INFORMATION)
if not self.plug_address:
self.logger.error("Plug address must be set")
return
try:
self.try_connect()
self.status_thread = threading.Thread(target=self.status_update)
self.status_thread.start()
if self.output_setup:
self.logger.debug('Strip setup: {}'.format(self.strip.hw_info))
for channel in channels_dict:
if self.options_channels['state_startup'][channel] == 1:
self.output_switch("on", output_channel=channel)
elif self.options_channels['state_startup'][channel] == 0:
self.output_switch("off", output_channel=channel)
self.logger.debug('Strip children: {}'.format(self.strip.children[channel]))
if (self.options_channels['state_startup'][channel] in [0, 1] and
self.options_channels['trigger_functions_startup'][channel]):
try:
self.check_triggers(self.unique_id, output_channel=channel)
except Exception as err:
self.logger.error(
f"Could not check Trigger for channel {channel} of output {self.unique_id}: {err}")
except Exception as e:
self.logger.error("initialize() Error: {err}".format(err=e))
def try_connect(self):
try:
from kasa import SmartStrip
self.strip = SmartStrip(self.plug_address)
asyncio.run(self.strip.update())
self.output_setup = True
except Exception as e:
if self.first_connect:
self.first_connect = False
self.logger.error("Output was unable to be setup: {err}".format(err=e))
else:
self.logger.debug("Output was unable to be setup: {err}".format(err=e))
def output_switch(self, state, output_type=None, amount=None, output_channel=None):
if not self.is_setup():
msg = "Error 101: Device not set up. See https://kizniche.github.io/Mycodo/Error-Codes#error-101 for more info."
self.logger.error(msg)
return msg
while self.outlet_status_checking and self.running:
time.sleep(0.1)
try:
self.outlet_switching = True
if state == 'on':
asyncio.run(self.strip.children[output_channel].turn_on())
self.output_states[output_channel] = True
elif state == 'off':
asyncio.run(self.strip.children[output_channel].turn_off())
self.output_states[output_channel] = False
msg = 'success'
except Exception as e:
msg = "State change error: {}".format(e)
self.logger.error(msg)
self.output_setup = False
finally:
self.outlet_switching = False
return msg
def is_on(self, output_channel=None):
if self.is_setup():
if output_channel is not None and output_channel in self.output_states:
return self.output_states[output_channel]
else:
return self.output_states
def is_setup(self):
return self.output_setup
def stop_output(self):
"""Called when Output is stopped."""
if self.is_setup():
for channel in channels_dict:
if self.options_channels['state_shutdown'][channel] == 1:
self.output_switch('on', output_channel=channel)
elif self.options_channels['state_shutdown'][channel] == 0:
self.output_switch('off', output_channel=channel)
self.running = False
def status_update(self):
while self.running:
if self.timer_status_check < time.time():
while self.timer_status_check < time.time():
self.timer_status_check += self.status_update_period
while self.outlet_switching and self.running:
time.sleep(0.1)
self.outlet_status_checking = True
self.logger.debug("Checking state of outlets")
if not self.output_setup:
self.try_connect()
if not self.output_setup:
self.logger.debug("Could not connect to power strip")
try:
if self.output_setup:
asyncio.run(self.strip.update())
for channel in channels_dict:
if self.strip.children[channel].is_on:
self.output_states[channel] = True
else:
self.output_states[channel] = False
except Exception as e:
self.logger.debug("Could not query power strip status: {}".format(e))
self.output_setup = False
finally:
self.outlet_status_checking = False
time.sleep(1)