Skip to content

Commit

Permalink
1.0.2 (#138)
Browse files Browse the repository at this point in the history
* resolve issues related to thermal runaway only being triggered once until a restart
* add uptime library to resolve issues with idle timeout and initial pi boot
* add label to warning prompts for better identification
* add LED control via [M150](https://reprap.org/wiki/G-code#M150:_Set_LED_color) gcode commands
  • Loading branch information
jneilliii authored Apr 9, 2021
2 parents 3251efd + 93b083b commit efcaa8b
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 27 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,11 @@ Once installed go into settings and enter the ip address for your TP-Link Smartp
With these options the raspberry pi will be shutdown 5 seconds after the idle timeout is reached (as configured on the main settings page) and send a backlog command to your Tasmota device to power off after a 60 second delay.

## Most recent changelog
**[1.0.1](https://github.com/jneilliii/OctoPrint-Tasmota/releases/tag/1.0.1)** (01/30/2021)
* fix regression related to special characters in passwords
* clear _autostart_file on print start to avoid re-printing canceled print on connecting to printer
* fix backlog off command using backlog on delay
**[1.0.2](https://github.com/jneilliii/OctoPrint-Tasmota/releases/tag/1.0.0)** (04/09/2021)
* add uptime library to resolve issues with idle timeout and initial pi boot
* add label to warning prompts for better identification
* resolve issues related to thermal runaway only being triggered once until a restart
* add LED control via [M150](https://reprap.org/wiki/G-code#M150:_Set_LED_color) gcode commands

### [All releases](https://github.com/jneilliii/OctoPrint-Tasmota/releases)

Expand Down
95 changes: 83 additions & 12 deletions octoprint_tasmota/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import threading
import sqlite3
import flask
from uptime import uptime
from datetime import datetime, timedelta

try:
Expand Down Expand Up @@ -224,7 +225,7 @@ def on_settings_save(self, data):
self.poll_status.start()

def get_settings_version(self):
return 10
return 11

def on_settings_migrate(self, target, current=None):
if current is None or current < 6:
Expand Down Expand Up @@ -260,6 +261,14 @@ def on_settings_migrate(self, target, current=None):
plug["event_on_connecting"] = False
arrSmartplugs_new.append(plug)
self._settings.set(["arrSmartplugs"], arrSmartplugs_new)
if current < 11:
# Add new fields
arrSmartplugs_new = []
for plug in self._settings.get(['arrSmartplugs']):
plug["is_led"] = False
plug["brightness"] = 50
arrSmartplugs_new.append(plug)
self._settings.set(["arrSmartplugs"], arrSmartplugs_new)

##~~ AssetPlugin mixin

Expand Down Expand Up @@ -327,6 +336,10 @@ def on_event(self, event, payload):

# Printer Connected Event
if event == Events.CONNECTED:
if self.thermal_runaway_triggered:
self._plugin_manager.send_plugin_message(self._identifier, dict(thermal_runaway=True, type="connection"))
self._tasmota_logger.debug("thermal runaway event triggered prior to last connection.")
self.thermal_runaway_triggered = False
if self._autostart_file:
self._tasmota_logger.debug("printer connected starting print of %s" % self._autostart_file)
self._printer.select_file(self._autostart_file, False, printAfterSelect=True)
Expand Down Expand Up @@ -355,6 +368,10 @@ def on_event(self, event, payload):
if event == Events.PRINT_STARTED and self._settings.getFloat(["cost_rate"]) > 0:
self.print_job_started = True
self._tasmota_logger.debug(payload.get("path", None))
if self.thermal_runaway_triggered:
self._plugin_manager.send_plugin_message(self._identifier, dict(thermal_runaway=True, type="connection"))
self._tasmota_logger.debug("thermal runaway event triggered prior to last connection.")
self.thermal_runaway_triggered = False
for plug in self._settings.get(["arrSmartplugs"]):
status = self.check_status(plug["ip"], plug["idx"])
self.print_job_power -= float(self.deep_get(status, ["energy_data", "Total"], default=0))
Expand Down Expand Up @@ -667,6 +684,18 @@ def gcode_off(self, plug):
def gcode_on(self, plug):
self.turn_on(plug["ip"], plug["idx"])

def gcode_led(self, plugip, led_data):
self._tasmota_logger.debug("Received LED Command for {} with parameters {}".format(plugip, led_data))
for plug in self._settings.get(["arrSmartplugs"]):
if plug["is_led"]:
if led_data["LEDBrightness"] == -1:
led_data["LEDBrightness"] = plug["brightness"]
try:
requests.get("http://{}/cm".format(plugip), params={"user": plug["username"], "password": plug["password"], "cmnd": "backlog dimmer {}; color2 {},{},{}; white {}; power{} on".format(led_data["LEDBrightness"], led_data["LEDRed"], led_data["LEDGreen"], led_data["LEDBlue"], led_data["LEDWhite"], plug["idx"])}, timeout=3)
self._plugin_manager.send_plugin_message(self._identifier, dict(currentState="on", ip=plug["ip"], idx=plug["idx"], color=led_data))
except Exception as e:
self._logger.debug("Error: {}".format(e))

def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwargs):
if gcode:
if gcode in ["M80", "M81"] and cmd.count(" ") >= 2:
Expand All @@ -690,6 +719,36 @@ def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwar
return
else:
return
elif gcode == "M150":
workleds = dict(LEDRed=0, LEDBlue=0, LEDGreen=0, LEDWhite=0, LEDBrightness=-1)
workval = cmd.upper().split()
for i in workval:
firstchar = str(i[0].upper())
leddata = str(i[1:].strip())
if not leddata.isdigit() and firstchar != 'I':
self._tasmota_logger.debug(leddata)
return

if firstchar == 'M':
continue
elif firstchar == "I":
plugip = leddata
elif firstchar == 'R':
workleds['LEDRed'] = int(leddata)
elif firstchar == 'G' or firstchar == 'U':
workleds['LEDGreen'] = int(leddata)
elif firstchar == 'B':
workleds['LEDBlue'] = int(leddata)
elif firstchar == "W":
workleds['LEDWhite'] = int(float(leddata)/255*100)
elif firstchar == "P":
workleds['LEDBrightness'] = int(float(leddata)/255*100)
else:
self._tasmota_logger.debug(leddata)

t = threading.Timer(0, self.gcode_led, [plugip, workleds])
t.daemon = True
t.start()
elif self.powerOffWhenIdle and not (gcode in self._idleIgnoreCommandsArray):
self._waitForHeaters = False
self._reset_idle_timer()
Expand All @@ -698,17 +757,23 @@ def processGCODE(self, comm_instance, phase, cmd, cmd_type, gcode, *args, **kwar
##~~ Temperatures received hook

def check_temps(self, parsed_temps):
for k, v in parsed_temps.items():
if k == "B" and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
self._tasmota_logger.debug("Max bed temp reached, shutting off plugs.")
self.thermal_runaway_triggered = True
if k.startswith("T") and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
self._tasmota_logger.debug("Extruder max temp reached, shutting off plugs.")
self.thermal_runaway_triggered = True
if self.thermal_runaway_triggered == True:
for plug in self._settings.get(['arrSmartplugs']):
if plug["thermal_runaway"] == True:
self.turn_off(plug["ip"], plug["idx"])
process_items = parsed_temps.items()
try:
for k, v in process_items:
if k == "B" and v[0] > int(self._settings.get(["thermal_runaway_max_bed"])):
self._tasmota_logger.debug("Max bed temp reached, shutting off plugs.")
self._plugin_manager.send_plugin_message(self._identifier, dict(thermal_runaway=True, type="bed"))
self.thermal_runaway_triggered = True
if k.startswith("T") and v[0] > int(self._settings.get(["thermal_runaway_max_extruder"])):
self._tasmota_logger.debug("Extruder max temp reached, shutting off plugs.")
self._plugin_manager.send_plugin_message(self._identifier, dict(thermal_runaway=True, type="extruder"))
self.thermal_runaway_triggered = True
if self.thermal_runaway_triggered == True:
for plug in self._settings.get(['arrSmartplugs']):
if plug["thermal_runaway"] == True:
self.turn_off(plug["ip"], plug["idx"])
except BaseException as e:
self._logger.debug(e)

def monitor_temperatures(self, comm, parsed_temps):
if self._settings.get(["thermal_runaway_monitoring"]) and self.thermal_runaway_triggered == False:
Expand Down Expand Up @@ -755,6 +820,12 @@ def _idle_poweroff(self):
if self._printer.is_printing() or self._printer.is_paused():
return

if (uptime() / 60) <= (self._settings.get_int(["idleTimeout"])):
self._tasmota_logger.debug("Just booted so wait for time sync.")
self._tasmota_logger.debug("uptime: {}, comparison: {}".format((uptime() / 60), (self._settings.get_int(["idleTimeout"]))))
self._reset_idle_timer()
return

self._tasmota_logger.debug(
"Idle timeout reached after %s minute(s). Turning heaters off prior to powering off plugs." % self.idleTimeout)
if self._wait_for_heaters():
Expand Down
5 changes: 5 additions & 0 deletions octoprint_tasmota/static/css/tasmota.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,8 @@ input[type="datetime-local"].alert-error {
background-color: #f2dede;
border-color: #b94a48;
}

#TasmotaEditor input[type=checkbox] {
margin-top: 0px;
vertical-align: baseline;
}
2 changes: 1 addition & 1 deletion octoprint_tasmota/static/js/plotly-latest.min.js

Large diffs are not rendered by default.

43 changes: 39 additions & 4 deletions octoprint_tasmota/static/js/tasmota.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ $(function() {
self.arrSmartplugs = ko.observableArray();
self.arrSmartplugsTooltips = ko.observableDictionary({});
self.arrSmartplugsStates = ko.observableDictionary({});
self.arrSmartplugsLEDColors = ko.observableDictionary({});
self.isPrinting = ko.observable(false);
self.gcodeOnString = function(data){return 'M80 '+data.ip()+' '+data.idx();};
self.gcodeOffString = function(data){return 'M81 '+data.ip()+' '+data.idx();};
Expand All @@ -23,9 +24,9 @@ $(function() {
self.get_color = function(data){
switch(self.arrSmartplugsStates.get(data.ip()+'_'+data.idx())()) {
case "on":
return data.on_color();
return self.arrSmartplugsLEDColors.get(data.ip()+'_'+data.idx())() ? self.arrSmartplugsLEDColors.get(data.ip()+'_'+data.idx())() : data.on_color();
case "off":
return data.off_color();
return self.arrSmartplugsLEDColors.get(data.ip()+'_'+data.idx())() ? data.unknown_color() : data.off_color();
default:
return data.unknown_color();
}
Expand Down Expand Up @@ -321,6 +322,36 @@ $(function() {
return;
}

if(data.hasOwnProperty("thermal_runaway")){
let message = "Thermal runaway was triggered "
switch (data.type){
case "connection":
message += "previously.";
break
case "bed":
message += "from the bed.";
break
case "extruder":
message += "from the extruder.";
break
default:
message = "Unknown thermal_runaway type.";
}

if (self.thermal_runaway_notice !== undefined) {
self.thermal_runaway_notice.update({text: message});
self.thermal_runaway_notice.open();
} else {
self.thermal_runaway_notice = new PNotify({
title: "Tasmota Error",
type: "error",
text: message,
hide: false
});
}
return;
}

if(data.hasOwnProperty("powerOffWhenIdle")) {
self.settings.settings.plugins.tasmota.powerOffWhenIdle(data.powerOffWhenIdle);

Expand Down Expand Up @@ -356,7 +387,7 @@ $(function() {
var tooltip = plug.label();
if(data.sensor_data) {
for(k in data.sensor_data) {
tooltip += '<br>' + k + ': ' + data.sensor_data[k]
tooltip += '<br>' + k + ': ' + data.sensor_data["k"]
}
}
if(data.energy_data) {
Expand All @@ -373,7 +404,7 @@ $(function() {
}
try {
self.arrSmartplugsStates.set(data.ip + '_' + data.idx, data.currentState);
}catch (error) {
} catch (error) {
self.processing.remove(data.ip);
console.log('currentState', error);
}
Expand All @@ -385,6 +416,10 @@ $(function() {
hide: true
});
}
if(data.color){
var color = (data.color.LEDBrightness > 0) ? 'RGB(' + data.color.LEDRed + ',' + data.color.LEDGreen + ',' + data.color.LEDBlue + ')' : plug["unknown_color"];
self.arrSmartplugsLEDColors.set(data.ip + '_' + data.idx, color);
}
self.processing.remove(data.ip);
}
};
Expand Down
2 changes: 1 addition & 1 deletion octoprint_tasmota/templates/tasmota_navbar.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</div>
<div class="modal-body">
<p>
<!--ko text: ip()--><!--/ko--> is currently <!--ko text: $root.arrSmartplugsStates.get(ip()+'_'+idx())()--><!--/ko-->.
<!--ko text: label() + ' (' + ip() + ')'--><!--/ko--> is currently <!--ko text: $root.arrSmartplugsStates.get(ip()+'_'+idx())()--><!--/ko-->.
</p>
<p>
{{ _('Are you sure you want to proceed?') }}
Expand Down
6 changes: 3 additions & 3 deletions octoprint_tasmota/templates/tasmota_settings.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -213,16 +213,16 @@
<td><div class="controls"><label class="control-label">{{ _('Off Color') }}</label><input type="color" class="input input-small" data-bind="value: off_color" /></div></td>
<td><div class="controls"><label class="control-label">{{ _('Unknown Color') }}</label><input type="color" class="input input-small" data-bind="value: unknown_color" /></div></td>
<td><div class="controls"><label class="control-label">{{ _('Icon Class') }}</label><input type="text" class="input-block-level" data-bind="value: icon, iconpicker: icon,iconpickerOptions: {hideOnSelect: true, collision: true}" /></div></td>
<td></td>
<td><div class="controls"><label class="control-label"><input type="checkbox" data-bind="checked: is_led" title="Is an LED or WS2812 type device connected." /> {{ _('LED') }}</label><div class="input-append"><input type="number" min="1" max="100" class="input-small" data-bind="value: brightness, visible: is_led" /><span class="add-on">%</span></div></div></td>
<td><div class="controls"><label class="control-label">{{ _('Sensor Identifier') }}</label><input type="text" class="input-block-level" data-bind="value: sensor_identifier" /></div></td>
</tr>
<tr>
<td><div class="controls"><label class="checkbox"><input type="checkbox" data-bind="checked: event_on_upload, enable: $root.settings.settings.plugins.tasmota.event_on_upload_monitoring()" title="Automatically power on when file is uploaded." /> On with Upload</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" data-bind="checked: event_on_connecting, enable: $root.settings.settings.plugins.tasmota.event_on_connecting_monitoring()" title="Automatically power on when pressing Connect button" /> On with Connect</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" data-bind="checked: automaticShutdownEnabled, enable: $root.settings.settings.plugins.tasmota.powerOffWhenIdle()" title="Automatically power off when printer is idle." /> Off on Idle</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" data-bind="checked: thermal_runaway" title="Power off if temperature exceeds configured max temperatures." /> Thermal Runaway</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" title="{{ _('Automatically power off this relay when Error Event Monitoring is enabled.') }}" data-toggle="tooltip" data-bind="checked: event_on_error, enable: $root.settings.settings.plugins.tasmota.event_on_error_monitoring(), tooltip: {}" disabled /> {{ _('Off on Error') }}</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" title="{{ _('Automatically power off this relay when Disconnect Event Monitoring is enabled.') }}" data-toggle="tooltip" data-bind="checked: event_on_disconnect, enable: $root.settings.settings.plugins.tasmota.event_on_disconnect_monitoring(), tooltip: {}" disabled /> {{ _('Off on Disconnect') }}</label></div></td>
<td><div class="controls" data-toggle="tooltip" data-bind="tooltip: {container: '#TasmotaEditor'}" title="{{ _('Automatically power off this relay when Error Event Monitoring is enabled.') }}"><label class="checkbox"><input type="checkbox" data-bind="checked: event_on_error, enable: $root.settings.settings.plugins.tasmota.event_on_error_monitoring()" disabled /> {{ _('Off on Error') }}</label></div></td>
<td><div class="controls"><label class="checkbox"><input type="checkbox" title="{{ _('Automatically power off this relay when Disconnect Event Monitoring is enabled.') }}" data-toggle="tooltip" data-bind="checked: event_on_disconnect, enable: $root.settings.settings.plugins.tasmota.event_on_disconnect_monitoring(), tooltip: {container: '#TasmotaEditor'}" disabled /> {{ _('Off on Disconnect') }}</label></div></td>
</tr>
<tr>
<td>
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
plugin_name = "OctoPrint-Tasmota"

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "1.0.1"
plugin_version = "1.0.2"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand All @@ -33,7 +33,7 @@
plugin_license = "AGPLv3"

# Any additional requirements besides OctoPrint should be listed here
plugin_requires = ["requests"]
plugin_requires = ["requests", "uptime"]

### --------------------------------------------------------------------------------------------------------------------
### More advanced options that you usually shouldn't have to touch follow after this point
Expand Down

0 comments on commit efcaa8b

Please sign in to comment.