Skip to content

Commit

Permalink
0.9.17
Browse files Browse the repository at this point in the history
### 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.
  • Loading branch information
jneilliii authored Oct 22, 2019
1 parent 58e073f commit 40ed69a
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 55 deletions.
10 changes: 10 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [0.9.17] = 2019-05-25
### Added
- Error and Disconnect event monitoring.

### 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.

## [0.9.16] - 2019-05-11
### Added
- Added HS107 and HS300 multiple plug support.
Expand Down Expand Up @@ -179,6 +188,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
- Initial release.

[0.9.17]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.17
[0.9.16]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.16
[0.9.13]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.13
[0.9.12]: https://github.com/jneilliii/OctoPrint-TPLinkSmartplug/tree/0.9.12
Expand Down
139 changes: 102 additions & 37 deletions octoprint_tplinksmartplug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
import sqlite3
from datetime import datetime
from struct import unpack
from builtins import bytes

class tplinksmartplugPlugin(octoprint.plugin.SettingsPlugin,
octoprint.plugin.AssetPlugin,
octoprint.plugin.TemplatePlugin,
octoprint.plugin.SimpleApiPlugin,
octoprint.plugin.StartupPlugin,
octoprint.plugin.ProgressPlugin):
octoprint.plugin.ProgressPlugin,
octoprint.plugin.EventHandlerPlugin):

def __init__(self):
self._logger = logging.getLogger("octoprint.plugins.tplinksmartplug")
Expand Down Expand Up @@ -55,12 +57,14 @@ def on_after_startup(self):
def get_settings_defaults(self):
return dict(
debug_logging = False,
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':{}}}],
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':{}}}],
pollingInterval = 15,
pollingEnabled = False,
thermal_runaway_monitoring = False,
thermal_runaway_max_bed = 0,
thermal_runaway_max_extruder = 0
thermal_runaway_max_extruder = 0,
event_on_error_monitoring = False,
event_on_disconnect_monitoring = False
)

def on_settings_save(self, data):
Expand All @@ -76,7 +80,7 @@ def on_settings_save(self, data):
self._tplinksmartplug_logger.setLevel(logging.INFO)

def get_settings_version(self):
return 9
return 10

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

if current is not None and current < 10:
arrSmartplugs_new = []
for plug in self._settings.get(['arrSmartplugs']):
plug["event_on_error"] = False
plug["event_on_disconnect"] = False
arrSmartplugs_new.append(plug)
self._settings.set(["arrSmartplugs"],arrSmartplugs_new)

##~~ AssetPlugin mixin

def get_assets(self):
Expand Down Expand Up @@ -143,16 +155,20 @@ def turn_on(self, plugip):
self._tplinksmartplug_logger.debug("Turning on %s." % plugip)
plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip)
self._tplinksmartplug_logger.debug(plug)
if plug["useCountdownRules"]:
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug["ip"])
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"])
if "/" in plugip:
plug_ip, plug_num = plugip.split("/")
else:
plug_ip = plugip
plug_num = -1
if plug["useCountdownRules"] and int(plug["countdownOnDelay"]) > 0:
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip, plug_num)
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"])
if chk == 0:
c = threading.Timer(int(plug["countdownOnDelay"])+5,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)])
c.start()
else:
turn_on_cmnd = dict(system=dict(set_relay_state=dict(state=1)))
plug_ip = plugip.split("/")
if len(plug_ip) == 2:
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip[0],plug_ip[1]),*["system","set_relay_state","err_code"])
else:
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip[0]),*["system","set_relay_state","err_code"])
chk = self.lookup(self.sendCommand(turn_on_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"])

self._tplinksmartplug_logger.debug(chk)
if chk == 0:
Expand All @@ -168,9 +184,17 @@ def turn_off(self, plugip):
self._tplinksmartplug_logger.debug("Turning off %s." % plugip)
plug = self.plug_search(self._settings.get(["arrSmartplugs"]),"ip",plugip)
self._tplinksmartplug_logger.debug(plug)
if plug["useCountdownRules"]:
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug["ip"])
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"])
if "/" in plugip:
plug_ip, plug_num = plugip.split("/")
else:
plug_ip = plugip
plug_num = -1
if plug["useCountdownRules"] and int(plug["countdownOffDelay"]) > 0:
self.sendCommand(json.loads('{"count_down":{"delete_all_rules":null}}'),plug_ip,plug_num)
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"])
if chk == 0:
c = threading.Timer(int(plug["countdownOnDelay"])+5,self._plugin_manager.send_plugin_message,[self._identifier, dict(check_status=True,ip=plugip)])
c.start()

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

if not plug["useCountdownRules"]:
turn_off_cmnd = dict(system=dict(set_relay_state=dict(state=0)))
plug_ip = plugip.split("/")
if len(plug_ip) == 2:
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip[0],plug_ip[1]),*["system","set_relay_state","err_code"])
else:
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip[0]),*["system","set_relay_state","err_code"])
chk = self.lookup(self.sendCommand(turn_off_cmnd,plug_ip,plug_num),*["system","set_relay_state","err_code"])

self._tplinksmartplug_logger.debug(chk)
if chk == 0:
Expand All @@ -203,8 +223,10 @@ def check_status(self, plugip):
response = self.sendCommand(check_status_cmnd, plug_ip[0], plug_ip[1])
else:
response = self.sendCommand(check_status_cmnd, plug_ip[0])

if "ENE" in self.lookup(response, *["system","get_sysinfo","feature"]):

self._tplinksmartplug_logger.debug(self.deep_get(response,["system","get_sysinfo","feature"], default=""))
if "ENE" in self.deep_get(response,["system","get_sysinfo","feature"], default=""):
# if "ENE" in self.lookup(response, *["system","get_sysinfo","feature"]):
emeter_data_cmnd = dict(emeter = dict(get_realtime = dict()))
if len(plug_ip) == 2:
check_emeter_data = self.sendCommand(emeter_data_cmnd, plug_ip[0], plug_ip[1])
Expand Down Expand Up @@ -290,6 +312,28 @@ def on_api_command(self, command, data):
response = dict(ip = data.ip, currentState = "unknown")
return flask.jsonify(response)

##~~ EventHandlerPlugin mixin

def on_event(self, event, payload):
if event == "PrinterStateChanged":
self._tplinksmartplug_logger.debug(payload["state_id"])
if event == "Error" and self._settings.getBoolean(["event_on_error_monitoring"]) == True:
self._tplinksmartplug_logger.debug("powering off due to %s event." % event)
for plug in self._settings.get(['arrSmartplugs']):
if plug["event_on_error"] == True:
self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event))
response = self.turn_off(plug["ip"])
if response["currentState"] == "off":
self._plugin_manager.send_plugin_message(self._identifier, response)
if event == "Disconnected" and self._settings.getBoolean(["event_on_disconnect_monitoring"]) == True:
self._tplinksmartplug_logger.debug("powering off due to %s event." % event)
for plug in self._settings.get(['arrSmartplugs']):
if plug["event_on_disconnect"] == True:
self._tplinksmartplug_logger.debug("powering off %s due to %s event." % (plug["ip"], event))
response = self.turn_off(plug["ip"])
if response["currentState"] == "off":
self._plugin_manager.send_plugin_message(self._identifier, response)

##~~ Utilities

def _get_device_id(self, plugip):
Expand Down Expand Up @@ -336,23 +380,42 @@ def plug_search(self, list, key, value):
if item[key] == value:
return item

# def encrypt(self, string):
# key = 171
# result = "\0\0\0"+chr(len(string))
# for i in string:
# a = key ^ ord(i)
# key = a
# result += chr(a)
# return result

# def decrypt(self, string):
# key = 171
# result = ""
# for i in string:
# a = key ^ ord(i)
# key = ord(i)
# result += chr(a)
# return result

def encrypt(self, string):
key = 171
result = "\0\0\0"+chr(len(string))
for i in string:
a = key ^ ord(i)
result = b"\0\0\0" + bytes([len(string)])
for i in bytes(string.encode('latin-1')):
a = key ^ i
key = a
result += chr(a)
result += bytes([a])
return result


def decrypt(self, string):
key = 171
result = ""
for i in string:
a = key ^ ord(i)
key = ord(i)
result += chr(a)
return result
key = 171
result = b""
for i in bytes(string):
a = key ^ i
key = i
result += bytes([a])
return result.decode('latin-1')

def sendCommand(self, cmd, plugip, plug_num = -1):
commands = {'info' : '{"system":{"get_sysinfo":{}}}',
Expand All @@ -367,7 +430,8 @@ def sendCommand(self, cmd, plugip, plug_num = -1):
'reboot' : '{"system":{"reboot":{"delay":1}}}',
'reset' : '{"system":{"reset":{"delay":1}}}'
}

if re.search('/\d+$', plugip):
self._tplinksmartplug_logger.exception("Internal error passing unsplit %s", plugip)
# try to connect via ip address
try:
socket.inet_aton(plugip)
Expand All @@ -383,7 +447,7 @@ def sendCommand(self, cmd, plugip, plug_num = -1):
self._tplinksmartplug_logger.debug("Invalid hostname %s." % plugip)
return {"system":{"get_sysinfo":{"relay_state":3}},"emeter":{"err_code": True}}

if plug_num >= 0:
if int(plug_num) >= 0:
plug_ip_num = plugip + "/" + plug_num
cmd["context"] = dict(child_ids = [self._get_device_id(plug_ip_num)])

Expand Down Expand Up @@ -464,10 +528,10 @@ def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwar
def check_temps(self, parsed_temps):
thermal_runaway_triggered = False
for k, v in parsed_temps.items():
if k == "B" and v[1] > 0 and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
if k == "B" and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
self._tplinksmartplug_logger.debug("Max bed temp reached, shutting off plugs.")
thermal_runaway_triggered = True
if k.startswith("T") and v[1] > 0 and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
if k.startswith("T") and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
self._tplinksmartplug_logger.debug("Extruder max temp reached, shutting off plugs.")
thermal_runaway_triggered = True
if thermal_runaway_triggered == True:
Expand Down Expand Up @@ -500,6 +564,7 @@ def get_update_information(self):
)

__plugin_name__ = "TP-Link Smartplug"
__plugin_pythoncompat__ = ">=2.7,<4"

def __plugin_load__():
global __plugin_implementation__
Expand Down
54 changes: 44 additions & 10 deletions octoprint_tplinksmartplug/static/js/tplinksmartplug.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,35 @@
* License: AGPLv3
*/
$(function() {
/* function plugViewModel() {
var self = this;
self.ip = ko.observable(ip);
self.label = ko.observable(label);
self.icon = ko.observable(icon);
self.displayWarning = ko.observable(displayWarning);
self.warnPrinting = ko.observable(warnPrinting);
self.gcodeEnabled = ko.observable(gcodeEnabled);
self.gcodeOnDelay = ko.observable(gcodeOnDelay);
self.gcodeOffDelay = ko.observable(gcodeOffDelay);
self.autoConnect = ko.observable(autoConnect);
self.autoConnectDelay = ko.observable(autoConnectDelay);
self.autoDisconnect = ko.observable(autoDisconnect);
self.autoDisconnectDelay = ko.observable(autoDisconnectDelay);
self.sysCmdOn = ko.observable(sysCmdOn);
self.sysRunCmdOn = ko.observable(sysRunCmdOn);
self.sysCmdOnDelay = ko.observable(sysCmdOnDelay);
self.sysCmdOff = ko.observable(sysCmdOff);
self.sysRunCmdOff = ko.observable(sysRunCmdOff);
self.sysCmdOffDelay = ko.observable(sysCmdOffDelay);
self.currentState = ko.observable(currentState);
self.btnColor = ko.observable(btnColor);
self.useCountdownRules = ko.observable(useCountdownRules);
self.countdownOnDelay = ko.observable(countdownOnDelay);
self.countdownOffDelay = ko.observable(countdownOffDelay);
self.emeter = {get_realtime = {}};
self.thermal_runaway = ko.observable(thermal_runaway)
} */

function tplinksmartplugViewModel(parameters) {
var self = this;

Expand Down Expand Up @@ -79,12 +108,14 @@ $(function() {
self.checkStatuses();
}

self.onSettingsBeforeSave = function() {
console.log('arrSmartplugs: ' + ko.toJSON(self.arrSmartplugs()));
console.log('settings.settings.plugins.tplinksmartplug.arrSmartplugs: ' + ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
if(ko.toJSON(self.arrSmartplugs()) !== ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs())){
self.onSettingsBeforeSave = function(payload) {
var plugs_updated = (ko.toJSON(self.arrSmartplugs()) !== ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
self.arrSmartplugs(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs());
if(plugs_updated){
console.log('onEventSettingsUpdated:');
console.log('arrSmartplugs: ' + ko.toJSON(self.arrSmartplugs()));
console.log('settings.settings.plugins.tplinksmartplug.arrSmartplugs: ' + ko.toJSON(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs()));
console.log('arrSmartplugs changed, checking statuses');
self.arrSmartplugs(self.settings.settings.plugins.tplinksmartplug.arrSmartplugs());
self.checkStatuses();
}
}
Expand Down Expand Up @@ -134,10 +165,12 @@ $(function() {
'currentState':ko.observable('unknown'),
'btnColor':ko.observable('#808080'),
'useCountdownRules':ko.observable(false),
'countdownOnDelay':ko.observable(0),
'countdownOffDelay':ko.observable(0),
'countdownOnDelay':ko.observable(1),
'countdownOffDelay':ko.observable(1),
'emeter':{get_realtime:{}},
'thermal_runaway':ko.observable(false)});
'thermal_runaway':ko.observable(false),
'event_on_error':ko.observable(false),
'event_on_disconnect':ko.observable(false)});
self.settings.settings.plugins.tplinksmartplug.arrSmartplugs.push(self.selectedPlug());
$("#TPLinkPlugEditor").modal("show");
}
Expand All @@ -150,8 +183,9 @@ $(function() {
if (plugin != "tplinksmartplug") {
return;
}
if(data.currentState){
//console.log('Websocket message received, checking status of ' + data.ip);

if(data.currentState || data.check_status){
// console.log('Websocket message received, checking status of ' + data.ip);
self.checkStatus(data.ip);
}
if(data.updatePlot && window.location.href.indexOf('tplinksmartplug') > 0){
Expand Down
Loading

0 comments on commit 40ed69a

Please sign in to comment.