diff --git a/octoprint/server.py b/octoprint/server.py
index 047d1f3..87f9ab1 100644
--- a/octoprint/server.py
+++ b/octoprint/server.py
@@ -75,14 +75,19 @@ class PrinterStateConnection(SockJSConnection):
self._logger.info("New connection from client: %s" % self._getRemoteAddress(info))
self._printer.registerCallback(self)
self._gcodeManager.registerCallback(self)
+ octoprint.timelapse.registerCallback(self)
self._eventManager.fire("ClientOpened")
self._eventManager.subscribe("MovieDone", self._onMovieDone)
+ global timelapse
+ octoprint.timelapse.notifyCallbacks(timelapse)
+
def on_close(self):
self._logger.info("Closed client connection")
self._printer.unregisterCallback(self)
self._gcodeManager.unregisterCallback(self)
+ octoprint.timelapse.unregisterCallback(self)
self._eventManager.fire("ClientClosed")
self._eventManager.unsubscribe("MovieDone", self._onMovieDone)
@@ -120,6 +125,9 @@ class PrinterStateConnection(SockJSConnection):
def sendFeedbackCommandOutput(self, name, output):
self._emit("feedbackCommandOutput", {"name": name, "output": output})
+ def sendTimelapseConfig(self, timelapseConfig):
+ self._emit("timelapse", timelapseConfig)
+
def addLog(self, data):
with self._logBacklogMutex:
self._logBacklog.append(data)
@@ -529,28 +537,56 @@ def deleteTimelapse(filename):
@app.route(BASEURL + "timelapse", methods=["POST"])
@restricted_access
def setTimelapseConfig():
- global timelapse
-
if request.values.has_key("type"):
- type = request.values["type"]
- if type in ["zchange", "timed"]:
- # valid timelapse type, check if there is an old one we need to stop first
- if timelapse is not None:
- timelapse.unload()
- timelapse = None
- if "zchange" == type:
- timelapse = octoprint.timelapse.ZTimelapse()
- elif "timed" == type:
+ config = {
+ "type": request.values["type"],
+ "options": {}
+ }
+
+ if request.values.has_key("interval"):
interval = 10
- if request.values.has_key("interval"):
- try:
- interval = int(request.values["interval"])
- except ValueError:
- pass
- timelapse = octoprint.timelapse.TimedTimelapse(interval)
+ try:
+ interval = int(request.values["interval"])
+ except ValueError:
+ pass
+
+ config["options"] = {
+ "interval": interval
+ }
+
+ if admin_permission.can() and request.values.has_key("save") and request.values["save"] in valid_boolean_trues:
+ _configureTimelapse(config, True)
+ else:
+ _configureTimelapse(config)
return getTimelapseData()
+def _configureTimelapse(config=None, persist=False):
+ global timelapse
+
+ if config is None:
+ config = settings().get(["webcam", "timelapse"])
+
+ if timelapse is not None:
+ timelapse.unload()
+
+ type = config["type"]
+ if type is None or "off" == type:
+ timelapse = None
+ elif "zchange" == type:
+ timelapse = octoprint.timelapse.ZTimelapse()
+ elif "timed" == type:
+ interval = 10
+ if "options" in config and "interval" in config["options"]:
+ interval = config["options"]["interval"]
+ timelapse = octoprint.timelapse.TimedTimelapse(interval)
+
+ octoprint.timelapse.notifyCallbacks(timelapse)
+
+ if persist:
+ settings().set(["webcam", "timelapse"], config)
+ settings().save()
+
#~~ settings
@app.route(BASEURL + "settings", methods=["GET"])
@@ -1056,6 +1092,9 @@ class Server():
gcodeManager = gcodefiles.GcodeManager()
printer = Printer(gcodeManager)
+ # configure timelapse
+ _configureTimelapse()
+
# setup system and gcode command triggers
events.SystemCommandTrigger(printer)
events.GcodeCommandTrigger(printer)
diff --git a/octoprint/settings.py b/octoprint/settings.py
index f95ed10..cad4c8c 100644
--- a/octoprint/settings.py
+++ b/octoprint/settings.py
@@ -46,7 +46,11 @@ default_settings = {
"bitrate": "5000k",
"watermark": True,
"flipH": False,
- "flipV": False
+ "flipV": False,
+ "timelapse": {
+ "type": "off",
+ "options": {}
+ }
},
"feature": {
"gCodeVisualizer": True,
diff --git a/octoprint/static/js/app/dataupdater.js b/octoprint/static/js/app/dataupdater.js
index b7bfdc3..919ea76 100644
--- a/octoprint/static/js/app/dataupdater.js
+++ b/octoprint/static/js/app/dataupdater.js
@@ -109,7 +109,11 @@ function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewM
}
case "feedbackCommandOutput": {
self.controlViewModel.fromFeedbackCommandData(payload);
- break
+ break;
+ }
+ case "timelapse": {
+ self.printerStateViewModel.fromTimelapseData(payload);
+ break;
}
}
}
diff --git a/octoprint/static/js/app/main.js b/octoprint/static/js/app/main.js
index 2bdba57..ee11483 100644
--- a/octoprint/static/js/app/main.js
+++ b/octoprint/static/js/app/main.js
@@ -5,13 +5,13 @@ $(function() {
var usersViewModel = new UsersViewModel(loginStateViewModel);
var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel);
var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel);
- var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel);
+ var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
+ var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel, timelapseViewModel);
var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
var terminalViewModel = new TerminalViewModel(loginStateViewModel, settingsViewModel);
var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel);
- var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
var gcodeViewModel = new GcodeViewModel(loginStateViewModel);
var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
diff --git a/octoprint/static/js/app/viewmodels/printerstate.js b/octoprint/static/js/app/viewmodels/printerstate.js
index 91bc505..6779d15 100644
--- a/octoprint/static/js/app/viewmodels/printerstate.js
+++ b/octoprint/static/js/app/viewmodels/printerstate.js
@@ -20,6 +20,7 @@ function PrinterStateViewModel(loginStateViewModel) {
self.printTime = ko.observable(undefined);
self.printTimeLeft = ko.observable(undefined);
self.sd = ko.observable(undefined);
+ self.timelapse = ko.observable(undefined);
self.filament = ko.observable(undefined);
self.estimatedPrintTime = ko.observable(undefined);
@@ -49,6 +50,22 @@ function PrinterStateViewModel(loginStateViewModel) {
return "Pause";
});
+ self.timelapseString = ko.computed(function() {
+ var timelapse = self.timelapse();
+
+ if (!timelapse || !timelapse.hasOwnProperty("type"))
+ return "-";
+
+ var type = timelapse["type"];
+ if (type == "zchange") {
+ return "On Z Change";
+ } else if (type == "timed") {
+ return "Timed (" + timelapse["options"]["interval"] + "s)";
+ } else {
+ return "-";
+ }
+ });
+
self.fromCurrentData = function(data) {
self._fromData(data);
}
@@ -57,6 +74,10 @@ function PrinterStateViewModel(loginStateViewModel) {
self._fromData(data);
}
+ self.fromTimelapseData = function(data) {
+ self.timelapse(data);
+ }
+
self._fromData = function(data) {
self._processStateData(data.state)
self._processJobData(data.job);
diff --git a/octoprint/static/js/app/viewmodels/timelapse.js b/octoprint/static/js/app/viewmodels/timelapse.js
index 6278fe2..a882df9 100644
--- a/octoprint/static/js/app/viewmodels/timelapse.js
+++ b/octoprint/static/js/app/viewmodels/timelapse.js
@@ -6,6 +6,9 @@ function TimelapseViewModel(loginStateViewModel) {
self.timelapseType = ko.observable(undefined);
self.timelapseTimedInterval = ko.observable(undefined);
+ self.persist = ko.observable(false);
+ self.isDirty = ko.observable(false);
+
self.isErrorOrClosed = ko.observable(undefined);
self.isOperational = ko.observable(undefined);
self.isPrinting = ko.observable(undefined);
@@ -16,11 +19,21 @@ function TimelapseViewModel(loginStateViewModel) {
self.intervalInputEnabled = ko.computed(function() {
return ("timed" == self.timelapseType());
- })
+ });
+ self.saveButtonEnabled = ko.computed(function() {
+ return self.isDirty() && self.isOperational() && !self.isPrinting() && self.loginState.isUser();
+ });
self.isOperational.subscribe(function(newValue) {
self.requestData();
- })
+ });
+
+ self.timelapseType.subscribe(function(newValue) {
+ self.isDirty(true);
+ });
+ self.timelapseTimedInterval.subscribe(function(newValue) {
+ self.isDirty(true);
+ });
// initialize list helper
self.listHelper = new ItemListHelper(
@@ -51,7 +64,7 @@ function TimelapseViewModel(loginStateViewModel) {
[],
[],
CONFIG_TIMELAPSEFILESPERPAGE
- )
+ );
self.requestData = function() {
$.ajax({
@@ -60,17 +73,20 @@ function TimelapseViewModel(loginStateViewModel) {
dataType: "json",
success: self.fromResponse
});
- }
+ };
self.fromResponse = function(response) {
self.timelapseType(response.type);
self.listHelper.updateItems(response.files);
if (response.type == "timed" && response.config && response.config.interval) {
- self.timelapseTimedInterval(response.config.interval)
+ self.timelapseTimedInterval(response.config.interval);
} else {
- self.timelapseTimedInterval(undefined)
+ self.timelapseTimedInterval(undefined);
}
+
+ self.persist(false);
+ self.isDirty(false);
}
self.fromCurrentData = function(data) {
@@ -97,12 +113,13 @@ function TimelapseViewModel(loginStateViewModel) {
type: "DELETE",
dataType: "json",
success: self.requestData
- })
+ });
}
- self.save = function() {
+ self.save = function(data, event) {
var data = {
- "type": self.timelapseType()
+ "type": self.timelapseType(),
+ "save": self.persist()
}
if (self.timelapseType() == "timed") {
@@ -115,6 +132,6 @@ function TimelapseViewModel(loginStateViewModel) {
dataType: "json",
data: data,
success: self.fromResponse
- })
+ });
}
}
diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2
index 8f98b86..abbf86a 100644
--- a/octoprint/templates/index.jinja2
+++ b/octoprint/templates/index.jinja2
@@ -116,6 +116,7 @@
File: (SD)
Filament:
Estimated Print Time:
+ Timelapse:
Height:
Print Time:
Print Time Left:
@@ -542,13 +543,19 @@