Skip to content

Commit 40ed69a

Browse files
authored
0.9.17
### Added - Error and Disconnect event monitoring. - Python 3 compatibility. ### Fixed - Countdown timers for multiple plug devices like the HS300. - Energy monitoring pull-down list now reflects newly added plugs. - Sidebar not displaying multiple plug devices.
1 parent 58e073f commit 40ed69a

File tree

6 files changed

+184
-55
lines changed

6 files changed

+184
-55
lines changed

changelog.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [0.9.17] = 2019-05-25
8+
### Added
9+
- Error and Disconnect event monitoring.
10+
11+
### Fixed
12+
- Countdown timers for multiple plug devices like the HS300.
13+
- Energy monitoring pull-down list now reflects newly added plugs.
14+
- Sidebar not displaying multiple plug devices.
15+
716
## [0.9.16] - 2019-05-11
817
### Added
918
- Added HS107 and HS300 multiple plug support.
@@ -179,6 +188,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
179188
### Added
180189
- Initial release.
181190

191+
[0.9.17]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.17
182192
[0.9.16]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.16
183193
[0.9.13]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.13
184194
[0.9.12]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.12

octoprint_tplinksmartplug/__init__.py

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
import sqlite3
1515
from datetime import datetime
1616
from struct import unpack
17+
from builtins import bytes
1718

1819
class tplinksmartplugPlugin(octoprint.plugin.SettingsPlugin,
1920
octoprint.plugin.AssetPlugin,
2021
octoprint.plugin.TemplatePlugin,
2122
octoprint.plugin.SimpleApiPlugin,
2223
octoprint.plugin.StartupPlugin,
23-
octoprint.plugin.ProgressPlugin):
24+
octoprint.plugin.ProgressPlugin,
25+
octoprint.plugin.EventHandlerPlugin):
2426

2527
def __init__(self):
2628
self._logger = logging.getLogger("octoprint.plugins.tplinksmartplug")
@@ -55,12 +57,14 @@ def on_after_startup(self):
5557
def get_settings_defaults(self):
5658
return dict(
5759
debug_logging = False,
58-
arrSmartplugs = [{'ip':'','label':'','icon':'icon-bolt','displayWarning':True,'warnPrinting':False,'thermal_runaway':False,'gcodeEnabled':False,'gcodeOnDelay':0,'gcodeOffDelay':0,'autoConnect':True,'autoConnectDelay':10.0,'autoDisconnect':True,'autoDisconnectDelay':0,'sysCmdOn':False,'sysRunCmdOn':'','sysCmdOnDelay':0,'sysCmdOff':False,'sysRunCmdOff':'','sysCmdOffDelay':0,'currentState':'unknown','btnColor':'#808080','useCountdownRules':False,'countdownOnDelay':0,'countdownOffDelay':0,'emeter':{'get_realtime':{}}}],
60+
arrSmartplugs = [{'ip':'','label':'','icon':'icon-bolt','displayWarning':True,'warnPrinting':False,'thermal_runaway':False,'event_on_error':False,'event_on_disconnect':False,'gcodeEnabled':False,'gcodeOnDelay':0,'gcodeOffDelay':0,'autoConnect':True,'autoConnectDelay':10.0,'autoDisconnect':True,'autoDisconnectDelay':0,'sysCmdOn':False,'sysRunCmdOn':'','sysCmdOnDelay':0,'sysCmdOff':False,'sysRunCmdOff':'','sysCmdOffDelay':0,'currentState':'unknown','btnColor':'#808080','useCountdownRules':False,'countdownOnDelay':1,'countdownOffDelay':1,'emeter':{'get_realtime':{}}}],
5961
pollingInterval = 15,
6062
pollingEnabled = False,
6163
thermal_runaway_monitoring = False,
6264
thermal_runaway_max_bed = 0,
63-
thermal_runaway_max_extruder = 0
65+
thermal_runaway_max_extruder = 0,
66+
event_on_error_monitoring = False,
67+
event_on_disconnect_monitoring = False
6468
)
6569

6670
def on_settings_save(self, data):
@@ -76,7 +80,7 @@ def on_settings_save(self, data):
7680
self._tplinksmartplug_logger.setLevel(logging.INFO)
7781

7882
def get_settings_version(self):
79-
return 9
83+
return 10
8084

8185
def on_settings_migrate(self, target, current=None):
8286
if current is None or current < 5:
@@ -115,6 +119,14 @@ def on_settings_migrate(self, target, current=None):
115119
arrSmartplugs_new.append(plug)
116120
self._settings.set(["arrSmartplugs"],arrSmartplugs_new)
117121

122+
if current is not None and current < 10:
123+
arrSmartplugs_new = []
124+
for plug in self._settings.get(['arrSmartplugs']):
125+
plug["event_on_error"] = False
126+
plug["event_on_disconnect"] = False
127+
arrSmartplugs_new.append(plug)
128+
self._settings.set(["arrSmartplugs"],arrSmartplugs_new)
129+
118130
##~~ AssetPlugin mixin
119131

120132
def get_assets(self):
@@ -143,16 +155,20 @@ def turn_on(self, plugip):
143155
self._tplinksmartplug_logger.debug("Turning on %s." % plugip)
144156
plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip)
145157
self._tplinksmartplug_logger.debug(plug)
146-
if plug["useCountdownRules"]:
147-
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug["ip"])
148-
chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":1,"name":"turn on"}}}' % plug["countdownOnDelay"]),plug["ip"]),*["count_down","add_rule","err_code"])
158+
if "/" in plugip:
159+
plug_ip, plug_num = plugip.split("/")
160+
else:
161+
plug_ip = plugip
162+
plug_num = -1
163+
if plug["useCountdownRules"] and int(plug["countdownOnDelay"]) > 0:
164+
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip, plug_num)
165+
chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":1,"name":"turn on"}}}' % plug["countdownOnDelay"]),plug_ip,plug_num),*["count_down","add_rule","err_code"])
166+
if chk == 0:
167+
c = threading.Timer(int(plug["countdownOnDelay"])+5,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)])
168+
c.start()
149169
else:
150170
turn_on_cmnd = dict(system=dict(set_relay_state=dict(state=1)))
151-
plug_ip = plugip.split("/")
152-
if len(plug_ip) == 2:
153-
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip[0],plug_ip[1]),*["system","set_relay_state","err_code"])
154-
else:
155-
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip[0]),*["system","set_relay_state","err_code"])
171+
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"])
156172

157173
self._tplinksmartplug_logger.debug(chk)
158174
if chk == 0:
@@ -168,9 +184,17 @@ def turn_off(self, plugip):
168184
self._tplinksmartplug_logger.debug("Turning off %s." % plugip)
169185
plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip)
170186
self._tplinksmartplug_logger.debug(plug)
171-
if plug["useCountdownRules"]:
172-
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug["ip"])
173-
chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":0,"name":"turn off"}}}' % plug["countdownOffDelay"]),plug["ip"]),*["count_down","add_rule","err_code"])
187+
if "/" in plugip:
188+
plug_ip, plug_num = plugip.split("/")
189+
else:
190+
plug_ip = plugip
191+
plug_num = -1
192+
if plug["useCountdownRules"] and int(plug["countdownOffDelay"]) > 0:
193+
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip,plug_num)
194+
chk = self.lookup(self.sendCommand(json.loads('{"count_down":{"add_rule":{"enable":1,"delay":%s,"act":0,"name":"turn off"}}}' % plug["countdownOffDelay"]),plug_ip,plug_num),*["count_down","add_rule","err_code"])
195+
if chk == 0:
196+
c = threading.Timer(int(plug["countdownOnDelay"])+5,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)])
197+
c.start()
174198

175199
if plug["sysCmdOff"]:
176200
t = threading.Timer(int(plug["sysCmdOffDelay"]),os.system,args=[plug["sysRunCmdOff"]])
@@ -181,11 +205,7 @@ def turn_off(self, plugip):
181205

182206
if not plug["useCountdownRules"]:
183207
turn_off_cmnd = dict(system=dict(set_relay_state=dict(state=0)))
184-
plug_ip = plugip.split("/")
185-
if len(plug_ip) == 2:
186-
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip[0],plug_ip[1]),*["system","set_relay_state","err_code"])
187-
else:
188-
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip[0]),*["system","set_relay_state","err_code"])
208+
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"])
189209

190210
self._tplinksmartplug_logger.debug(chk)
191211
if chk == 0:
@@ -203,8 +223,10 @@ def check_status(self, plugip):
203223
response = self.sendCommand(check_status_cmnd, plug_ip[0], plug_ip[1])
204224
else:
205225
response = self.sendCommand(check_status_cmnd, plug_ip[0])
206-
207-
if "ENE" in self.lookup(response, *["system","get_sysinfo","feature"]):
226+
227+
self._tplinksmartplug_logger.debug(self.deep_get(response,["system","get_sysinfo","feature"], default=""))
228+
if "ENE" in self.deep_get(response,["system","get_sysinfo","feature"], default=""):
229+
# if "ENE" in self.lookup(response, *["system","get_sysinfo","feature"]):
208230
emeter_data_cmnd = dict(emeter = dict(get_realtime = dict()))
209231
if len(plug_ip) == 2:
210232
check_emeter_data = self.sendCommand(emeter_data_cmnd, plug_ip[0], plug_ip[1])
@@ -290,6 +312,28 @@ def on_api_command(self, command, data):
290312
response = dict(ip = data.ip, currentState = "unknown")
291313
return flask.jsonify(response)
292314

315+
##~~ EventHandlerPlugin mixin
316+
317+
def on_event(self, event, payload):
318+
if event == "PrinterStateChanged":
319+
self._tplinksmartplug_logger.debug(payload["state_id"])
320+
if event == "Error" and self._settings.getBoolean(["event_on_error_monitoring"]) == True:
321+
self._tplinksmartplug_logger.debug("powering off due to %s event." % event)
322+
for plug in self._settings.get(['arrSmartplugs']):
323+
if plug["event_on_error"] == True:
324+
self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event))
325+
response = self.turn_off(plug["ip"])
326+
if response["currentState"] == "off":
327+
self._plugin_manager.send_plugin_message(self._identifier, response)
328+
if event == "Disconnected" and self._settings.getBoolean(["event_on_disconnect_monitoring"]) == True:
329+
self._tplinksmartplug_logger.debug("powering off due to %s event." % event)
330+
for plug in self._settings.get(['arrSmartplugs']):
331+
if plug["event_on_disconnect"] == True:
332+
self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event))
333+
response = self.turn_off(plug["ip"])
334+
if response["currentState"] == "off":
335+
self._plugin_manager.send_plugin_message(self._identifier, response)
336+
293337
##~~ Utilities
294338

295339
def _get_device_id(self, plugip):
@@ -336,23 +380,42 @@ def plug_search(self, list, key, value):
336380
if item[key] == value:
337381
return item
338382

383+
# def encrypt(self, string):
384+
# key = 171
385+
# result = "\0\0\0"+chr(len(string))
386+
# for i in string:
387+
# a = key ^ ord(i)
388+
# key = a
389+
# result += chr(a)
390+
# return result
391+
392+
# def decrypt(self, string):
393+
# key = 171
394+
# result = ""
395+
# for i in string:
396+
# a = key ^ ord(i)
397+
# key = ord(i)
398+
# result += chr(a)
399+
# return result
400+
339401
def encrypt(self, string):
340402
key = 171
341-
result = "\0\0\0"+chr(len(string))
342-
for i in string:
343-
a = key ^ ord(i)
403+
result = b"\0\0\0" + bytes([len(string)])
404+
for i in bytes(string.encode('latin-1')):
405+
a = key ^ i
344406
key = a
345-
result += chr(a)
407+
result += bytes([a])
346408
return result
347409

410+
348411
def decrypt(self, string):
349-
key = 171
350-
result = ""
351-
for i in string:
352-
a = key ^ ord(i)
353-
key = ord(i)
354-
result += chr(a)
355-
return result
412+
key = 171
413+
result = b""
414+
for i in bytes(string):
415+
a = key ^ i
416+
key = i
417+
result += bytes([a])
418+
return result.decode('latin-1')
356419

357420
def sendCommand(self, cmd, plugip, plug_num = -1):
358421
commands = {'info' : '{"system":{"get_sysinfo":{}}}',
@@ -367,7 +430,8 @@ def sendCommand(self, cmd, plugip, plug_num = -1):
367430
'reboot' : '{"system":{"reboot":{"delay":1}}}',
368431
'reset' : '{"system":{"reset":{"delay":1}}}'
369432
}
370-
433+
if re.search('/\d+$', plugip):
434+
self._tplinksmartplug_logger.exception("Internal error passing unsplit %s", plugip)
371435
# try to connect via ip address
372436
try:
373437
socket.inet_aton(plugip)
@@ -383,7 +447,7 @@ def sendCommand(self, cmd, plugip, plug_num = -1):
383447
self._tplinksmartplug_logger.debug("Invalid hostname %s." % plugip)
384448
return {"system":{"get_sysinfo":{"relay_state":3}},"emeter":{"err_code": True}}
385449

386-
if plug_num >= 0:
450+
if int(plug_num) >= 0:
387451
plug_ip_num = plugip + "/" + plug_num
388452
cmd["context"] = dict(child_ids = [self._get_device_id(plug_ip_num)])
389453

@@ -464,10 +528,10 @@ def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwar
464528
def check_temps(self, parsed_temps):
465529
thermal_runaway_triggered = False
466530
for k, v in parsed_temps.items():
467-
if k == "B" and v[1] > 0 and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
531+
if k == "B" and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
468532
self._tplinksmartplug_logger.debug("Max bed temp reached, shutting off plugs.")
469533
thermal_runaway_triggered = True
470-
if k.startswith("T") and v[1] > 0 and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
534+
if k.startswith("T") and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
471535
self._tplinksmartplug_logger.debug("Extruder max temp reached, shutting off plugs.")
472536
thermal_runaway_triggered = True
473537
if thermal_runaway_triggered == True:
@@ -500,6 +564,7 @@ def get_update_information(self):
500564
)
501565

502566
__plugin_name__ = "TP-Link Smartplug"
567+
__plugin_pythoncompat__ = ">=2.7,<4"
503568

504569
def __plugin_load__():
505570
global __plugin_implementation__

octoprint_tplinksmartplug/static/js/tplinksmartplug.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@
55
* License: AGPLv3
66
*/
77
$(function() {
8+
/* function plugViewModel() {
9+
var self = this;
10+
self.ip = ko.observable(ip);
11+
self.label = ko.observable(label);
12+
self.icon = ko.observable(icon);
13+
self.displayWarning = ko.observable(displayWarning);
14+
self.warnPrinting = ko.observable(warnPrinting);
15+
self.gcodeEnabled = ko.observable(gcodeEnabled);
16+
self.gcodeOnDelay = ko.observable(gcodeOnDelay);
17+
self.gcodeOffDelay = ko.observable(gcodeOffDelay);
18+
self.autoConnect = ko.observable(autoConnect);
19+
self.autoConnectDelay = ko.observable(autoConnectDelay);
20+
self.autoDisconnect = ko.observable(autoDisconnect);
21+
self.autoDisconnectDelay = ko.observable(autoDisconnectDelay);
22+
self.sysCmdOn = ko.observable(sysCmdOn);
23+
self.sysRunCmdOn = ko.observable(sysRunCmdOn);
24+
self.sysCmdOnDelay = ko.observable(sysCmdOnDelay);
25+
self.sysCmdOff = ko.observable(sysCmdOff);
26+
self.sysRunCmdOff = ko.observable(sysRunCmdOff);
27+
self.sysCmdOffDelay = ko.observable(sysCmdOffDelay);
28+
self.currentState = ko.observable(currentState);
29+
self.btnColor = ko.observable(btnColor);
30+
self.useCountdownRules = ko.observable(useCountdownRules);
31+
self.countdownOnDelay = ko.observable(countdownOnDelay);
32+
self.countdownOffDelay = ko.observable(countdownOffDelay);
33+
self.emeter = {get_realtime = {}};
34+
self.thermal_runaway = ko.observable(thermal_runaway)
35+
} */
36+
837
function tplinksmartplugViewModel(parameters) {
938
var self = this;
1039

@@ -79,12 +108,14 @@ $(function() {
79108
self.checkStatuses();
80109
}
81110

82-
self.onSettingsBeforeSave = function() {
83-
console.log('arrSmartplugs: ' + ko.toJSON(self.arrSmartplugs()));
84-
console.log('settings.settings.plugins.tplinksmartplug.arrSmartplugs: ' + ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
85-
if(ko.toJSON(self.arrSmartplugs()) !== ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs())){
111+
self.onSettingsBeforeSave = function(payload) {
112+
var plugs_updated = (ko.toJSON(self.arrSmartplugs()) !== ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
113+
self.arrSmartplugs(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs());
114+
if(plugs_updated){
115+
console.log('onEventSettingsUpdated:');
116+
console.log('arrSmartplugs: ' + ko.toJSON(self.arrSmartplugs()));
117+
console.log('settings.settings.plugins.tplinksmartplug.arrSmartplugs: ' + ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
86118
console.log('arrSmartplugs changed, checking statuses');
87-
self.arrSmartplugs(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs());
88119
self.checkStatuses();
89120
}
90121
}
@@ -134,10 +165,12 @@ $(function() {
134165
'currentState':ko.observable('unknown'),
135166
'btnColor':ko.observable('#808080'),
136167
'useCountdownRules':ko.observable(false),
137-
'countdownOnDelay':ko.observable(0),
138-
'countdownOffDelay':ko.observable(0),
168+
'countdownOnDelay':ko.observable(1),
169+
'countdownOffDelay':ko.observable(1),
139170
'emeter':{get_realtime:{}},
140-
'thermal_runaway':ko.observable(false)});
171+
'thermal_runaway':ko.observable(false),
172+
'event_on_error':ko.observable(false),
173+
'event_on_disconnect':ko.observable(false)});
141174
self.settings.settings.plugins.tplinksmartplug.arrSmartplugs.push(self.selectedPlug());
142175
$("#TPLinkPlugEditor").modal("show");
143176
}
@@ -150,8 +183,9 @@ $(function() {
150183
if (plugin != "tplinksmartplug") {
151184
return;
152185
}
153-
if(data.currentState){
154-
//console.log('Websocket message received, checking status of ' + data.ip);
186+
187+
if(data.currentState || data.check_status){
188+
// console.log('Websocket message received, checking status of ' + data.ip);
155189
self.checkStatus(data.ip);
156190
}
157191
if(data.updatePlot && window.location.href.indexOf('tplinksmartplug') > 0){

0 commit comments

Comments
 (0)