Dashboard Plugin

Thanks! That cleared up a lot for me. I now have most of the data and a way to make the dashboard dynamic depending on the current state:

Connected, Idle:

Printing:

I'm currently investigating how to get the fan speed and current layer. It looks like I will have to write that myself as it doesn't seem to be available as properties.

Next will be some styling to make it pretty. Time to learn some css I guess.

Rafaël is pretty decent if you want to roll your own graphics.

Rafaël seems nifty. I will have to play around with a bit I think.

I have so far managed to create a grid-based layout and added some icons:

There is a fair bit of clean-up left but I think I'm getting there eventually.

3 Likes

Looking good. You can also use plotly, c3/d3, or what's bundled with OctoPrint for the temperature graph, which I think is flot.

Nice. I will take a look at the graph next.

I have polished the UI a bit and made sure that it plays nice with Themeify:

I have also pushed a new revision to https://github.com/StefanCohen/OctoPrint-Dashboard in case someone wants to take a look at the current WIP. I have only tested against the virtual printer so far. I guess I should take it for a drive against my printer to see how it plays with Klipper next.

2 Likes

Not sure on the Klipper stuff since I haven't ever played with that, but if OctoPrint's other viewmodels work then I don't see any reason why your plugin won't. The virtual printer acts exactly like an attached marlin printer, so the assumption is since you bound to the other viewmodels it will work.

Well. The installer worked on my "production" OctoPrint and the dashboard apparently works really nice with Klipper. Other good news is that it plays well with the PrintTimeGenius plugin too, so now I can get some more reliable estimates.

Here's a snapshot during a calibration cube print on an Ender3 with an SKR 1.3 running Klipper:

I have customised the css using Themeify to fill the browser window horizontally and the Dashboard works with that too.

4 Likes

You are now officially an OctoPrint Plugin developer. Nice work.

3 Likes

Thanks a lot. I'm really only guessing my way around and feel lucky that my google-fu is yielding usable results :smiley:

Now for something more interesting: The Fan Speed has been hard coded as a placeholder so far and the print height is using the unreliable built-in method. I have been looking at https://github.com/OllisGit/OctoPrint-DisplayLayerProgress that has better ways to get fan speed, current layer/total layers, and current height/total height thru g-code parsing. This plugin also exposes API extensions for all of the above.

  1. Is it possible to introduce a dependency to another plugin like DisplayLayerProgress and have my plugin installing another plugin? Is this what plugin_requires in setup.py does?
  2. If possible, is it a good idea to introduce a dependency like that or would I have to duplicate the code for my dashboard?
  3. If I take that path, would it be possible to use the another plugins ViewModels or would I have to rely on the API?

Dependencies on other plugins is tricky. Unfortunately, I don't think you can use the plugin_requires in setup.py because it isn't able to load from pip wheels (or something like that). It covers stuff like python libraries that you can include, ie numpy in my BedLevelVisualizer. You can reference the other plugin's viewmodel as an optional dependency in your js file, and then if it's there use the API calls from it. I have done that before in order to check against if touchui is installed or not. Here's an example of that.

Basically what it does is add the viewmodel of the other plugin as a dependency and if it's there I don't load my view model because of issues with touchui. Of course your use case is a little different, but the logic is roughly the same.

Thanks! I'm not sure that I follow everything yet but I will give it a try. I noticed that DisplayLayerProgress also exposes events: https://github.com/OllisGit/OctoPrint-DisplayLayerProgress#developer---section

Would that be a better alternative to using the API? I haven't played with events yet, so I'm not quite sure about how I would use it. Are there code examples available somewhere?

Also, I've added the built-in progress bar for now. Here it is with the Discoranged theme:

It looks allright, but I will explore the other options next.

1 Like

I'm not fluent enough to figure this out myself. Is it possible to make the CSS adaptive to Themeify themes or is it necessary to update the themes to be aware of the dashboard (or add custom styles in the Themeify settings)?

Example: If I go this route and add highlight colours to for example times and target temps, Can I somehow use the highlight color (orange in this case) from the current theme if Themify is installed?

1 Like

I'll give it a try when I've finish my running print job...

I'm running into some kind of problem when trying to reference the Displaylayerprogress plugins ViewModel

Displaylayerprogress declares the ViewModel like this:

    OCTOPRINT_VIEWMODELS.push({
        construct: DisplaylayerprogressViewModel,
        dependencies: ["loginStateViewModel", "settingsViewModel"],
        elements: [
            document.getElementById("displayLayerProgress_plugin_navbar"),
            document.getElementById("displayLayerProgress_plugin_settings")
        ]
    });

DisplayLayerProgress.js has an observable attribute:

self.navBarMessage = ko.observable();

I'm trying this in my ViewModel:

        self.temperatureModel = parameters[0];
        self.printerStateModel = parameters[1];
        self.printerProfilesModel = parameters[2];
        self.connectionModel = parameters[3];
        self.DisplaylayerprogressViewModel = parameters[4];
...

    OCTOPRINT_VIEWMODELS.push({
        construct: DashboardViewModel,
        dependencies: [  "temperatureViewModel", "printerStateViewModel", "printerProfilesViewModel", "connectionViewModel" ],
        optional: ["DisplaylayerprogressViewModel"],
        elements: [ "#tab_plugin_dashboard" ]
    });

And in my template:

<span data-bind="attr: { title: 'DisplaylayerprogressInfo' }, html: DisplaylayerprogressViewModel.navBarMessage()"></span>

But I get a "Could not bind view model error" when trying this. Is there anything special I need to do when trying to reference a view model from another plugin?

Try adding it to the dependencies as well as the optional.

Hmm. I found this in the docs:

Additionally each plugin’s viewmodel will be added to the viewmodel map used for resolving dependencies as well, using the viewmodel’s class name with a lower case first character as identifier (so “MyCustomViewModel” will be registered for dependency injection as “myCustomViewModel”).

So i changed the reference to "displaylayerprogressViewModel" but there is still a problem.

It works as expected as long as I reference the ViewModel as a dependency but it fails if I try to inject it as optional. Having it in both dependencies and optional at the same time works. Strange. I'll try to uninstall displaylayerprogress to see if it still works.

Edit: Is this a bug or a problem with the documentation?

The documentation examples indicates that a ViewModel constructor can take another ViewModel either as a dependency (fails if not found) or as optional (continues if not found) but not both at the same time. Your example and mine indicates that it must be added to both. Am I missing something here?

That's the only way I could get it to work, by having it in both. Not sure if it's a bug or documentation issue. Maybe @foosel could chime in here.

I'm stuck and it is probably something trivial.

It doesn't seem like self.currentLayer gets updated in the event listner: It is stuck at "test" regardless of what I have tried:

$(function() {
    function DashboardViewModel(parameters) {
        var self = this;

        // self.loginStateViewModel = parameters[0];
        // self.settingsViewModel = parameters[1];
        self.temperatureModel = parameters[0];
        self.printerStateModel = parameters[1];
        self.printerProfilesModel = parameters[2];
        self.connectionModel = parameters[3];
        self.settingsViewModel = parameters[4];
        self.displaylayerprogressViewModel = parameters[5];

        self.currentLayer = ko.observable("test");

        if (!self.displaylayerprogressViewModel) {
                console.log("displaylayerprogressViewModel is not loaded. Is plugin installed?");
        }


        self.onDataUpdaterPluginMessage = function(plugin, data) {
                if (plugin != "dashboard") {
                        return;
                        }
                if (data.currentLayer) {
                        console.log(data.currentLayer);
                        self.currentLayer = data.currentLayer;
                }
        }


         self.formatConnectionstatus = function(currentStatus) {
            if (currentStatus) {
                return "Connected";
            }
            else return "Disconnected";
        };
    }

    /* view model class, parameters for constructor, container to bind to  */
    OCTOPRINT_VIEWMODELS.push({
        construct: DashboardViewModel,
        dependencies: [  "temperatureViewModel", "printerStateViewModel", "printerProfilesViewModel", "connectionViewModel", "settingsViewModel", "displaylayerprogressViewModel" ],
        optional: [ "displaylayerprogressViewModel" ],
        elements: [ "#tab_plugin_dashboard" ]
    });
});

Any ideas?

onDataUpdaterPluginMessage only receives data that is pushed from the plugin with send_plugin_message callback. Looking at the developer section of @OllisGit's plugin it seems you may need to subscribe to an event instead or use octoprint.plugin.EventHandlerPlugin with something like this:

def on_event(self, event, payload):
    if event == "DisplayLayerProgress_layerChanged":
        ## do something usefull

My event listner works and the messages are passed on as expected. console.log(data.currentLayer); in the onDataUpdaterPluginMessage function logs the message to the browser console so I'm confident about that part.

What confuses me is that I can't understand why self.currentLayer = data.currentLayer;isn't passed on to the knockout binding despite having set self.currentLayer = ko.observable("test");.

In the template I have:

<span class="dashboardSmall" id="DisplaylayerprogressInfo" data-bind="attr: { title: 'DisplaylayerprogressInfo' }, html: currentLayer"></span>

This is probably due to my lack of understanding of how the template bindings work.
I have pushed the current version to https://github.com/StefanCohen/OctoPrint-Dashboard now.