forked from thernst-de/smarthome.plugin.autoblind
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AutoBlindAction.py
249 lines (224 loc) · 9.59 KB
/
AutoBlindAction.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
#!/usr/bin/env python3
# vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
#########################################################################
# Copyright 2014-2015 Thomas Ernst [email protected]
#########################################################################
# This file is part of SmartHome.py.
#
# SmartHome.py is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SmartHome.py is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SmartHome.py. If not, see <http://www.gnu.org/licenses/>.
#########################################################################
from . import AutoBlindTools
from . import AutoBlindLogger
from . import AutoBlindEval
# Class representing a single action
class AbAction:
# Initialize the action
# smarthome: Instance of smarthome.py-class
# name: Name of action
def __init__(self, smarthome, name: str):
self.__sh = smarthome
self.__name = name
self.__item = None
self.__value = None
self.__eval = None
self.__from_item = None
self.__byattr = None
self.__mindelta = None
self.__logic = None
self.__isRun = False
# set the action based on a set_(action_name) attribute
# item_state: state item to read from
# value: Value of the set_(action_name) attribute
def update_set(self, item_state, value):
if self.__item is None:
self.__set_item(AutoBlindTools.find_attribute(self.__sh, item_state, "as_item_" + self.__name))
func, set_value = AutoBlindTools.partition_strip(value, ":")
if set_value == "":
set_value = func
func = "value"
if func == "value":
self.__value = set_value
self.__eval = None
self.__from_item = None
self.__byattr = None
elif func == "eval":
self.__value = None
self.__eval = set_value
self.__from_item = None
self.__byattr = None
elif func == "item":
self.__value = None
self.__eval = None
self.__set_from_item(set_value)
self.__byattr = None
elif func == "byattr":
self.__value = None
self.__eval = None
self.__from_item = None
self.__byattr = set_value
self.__logic = None
self.__isRun = False
# set the action based on a trigger_(name) attribute
# value: Value of the trigger_(action_name) attribute
def update_trigger(self, value):
logic, value = AutoBlindTools.partition_strip(value, ":")
self.__logic = logic
if value != "":
self.__value = value
else:
self.__value = None
self.__eval = None
self.__from_item = None
self.__byattr = None
self.__isRun = False
# set the action based on a run_(action_name) attribute
# value: Value of the set_(action_name) attribute
def update_run(self, value):
func, set_value = AutoBlindTools.partition_strip(value, ":")
if set_value == "":
set_value = func
func = "eval"
if func == "eval":
self.__value = None
self.__eval = set_value
self.__from_item = None
self.__byattr = None
self.__isRun = True
self.__logic = None
# Complete action
# item_state: state item to read from
def complete(self, item_state):
# Nothing to complete if this action triggers a logic or is a "run"-Action or if it's a set-byattr Action
if self.__logic is not None or self.__isRun or self.__byattr is not None:
return
# missing item in action: Try to find it.
if self.__item is None:
result = AutoBlindTools.find_attribute(self.__sh, item_state, "as_item_" + self.__name)
if result is not None:
self.__set_item(result)
if self.__mindelta is None:
result = AutoBlindTools.find_attribute(self.__sh, item_state, "as_mindelta_" + self.__name)
if result is not None:
self.__mindelta = result
if self.__item is not None:
if self.__value is not None:
self.__value = self.__item.cast(self.__value)
if self.__mindelta is not None:
self.__mindelta = self.__item.cast(self.__mindelta)
# Execute action
# logger: Instance of AbLogger to write to
def execute(self, logger: AutoBlindLogger.AbLogger):
if self.__logic is not None:
# Trigger logic
logger.info("Action '{0}: Triggering logic '{1}' using value '{2}'.", self.__name, self.__logic,
self.__value)
self.__sh.trigger(self.__logic, by="AutoBlind Plugin", source=self.__name, value=self.__value)
return
if self.__byattr is not None:
logger.info("Action '{0}: Setting values by attribute '{1}'.", self.__name, self.__byattr)
for item in self.__sh.find_items(self.__byattr):
logger.info("\t{0} = {1}", item.id(), item.conf[self.__byattr])
item(item.conf[self.__byattr])
return
if self.__item is None and not self.__isRun:
logger.info("Action '{0}: No item defined. Ignoring.", self.__name)
return
value = None
if self.__value is not None:
value = self.__value
elif self.__eval is not None:
value = self.__do_eval(logger)
elif self.__from_item is not None:
# noinspection PyCallingNonCallable
value = self.__from_item()
if value is not None and not self.__isRun:
if self.__mindelta is not None:
# noinspection PyCallingNonCallable
delta = float(abs(self.__item() - value))
if delta < self.__mindelta:
logger.debug(
"Action '{0}: Not setting '{1}' to '{2}' because delta '{3:.2}' is lower than mindelta '{4}'",
self.__name, self.__item.id(), value, delta, self.__mindelta)
return
logger.debug("Action '{0}: Set '{1}' to '{2}'", self.__name, self.__item.id(), value)
# noinspection PyCallingNonCallable
self.__item(value)
# Write action to logger
# logger: Instance of AbLogger to write to
def write_to_logger(self, logger: AutoBlindLogger.AbLogger):
if self.__logic is not None:
logger.debug("trigger logic: {0}", self.__logic)
if self.__item is not None:
logger.debug("item: {0}", self.__item.id())
if self.__mindelta is not None:
logger.debug("mindelta: {0}", self.__mindelta)
if self.__value is not None:
logger.debug("value: {0}", self.__value)
if self.__eval is not None:
logger.debug("eval: {0}", self.__get_eval_name())
if self.__from_item is not None:
logger.debug("value from item: {0}", self.__from_item.id())
if self.__byattr is not None:
logger.debug("set by attriute: {0}", self.__byattr)
# Execute eval and return result. In case of errors, write message to log and return None
# logger: Instance of AbLogger to write to
def __do_eval(self, logger: AutoBlindLogger.AbLogger):
if isinstance(self.__eval, str):
# noinspection PyUnusedLocal
sh = self.__sh
if self.__eval.startswith("autoblind_eval"):
# noinspection PyUnusedLocal
autoblind_eval = AutoBlindEval.AbEval(self.__sh, logger)
try:
value = eval(self.__eval)
except Exception as e:
logger.info("Action '{0}: problem evaluating {1}: {2}.", self.__name, self.__get_eval_name(), e)
return None
else:
try:
# noinspection PyCallingNonCallable
value = self.__eval()
except Exception as e:
logger.info("Action '{0}: problem calling {1}: {2}.", self.__name, self.__get_eval_name(), e)
return None
try:
if self.__item is not None:
value = self.__item.cast(value)
return value
except Exception as e:
logger.debug("eval returned '{0}', trying to cast this returned exception '{1}'", value, e)
return None
# set item
# item: value for item
def __set_item(self, item):
if isinstance(item, str):
self.__item = self.__sh.return_item(item)
else:
self.__item = item
# set from-item
# from_item: value for from-item
def __set_from_item(self, from_item):
if isinstance(from_item, str):
self.__from_item = self.__sh.return_item(from_item)
else:
self.__from_item = from_item
# Name of eval-object to be displayed in log
def __get_eval_name(self):
if self.__eval is None:
return None
if self.__eval is not None:
if isinstance(self.__eval, str):
return self.__eval
else:
return self.__eval.__module__ + "." + self.__eval.__name__