Dashboard Plugin

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.

Damn I feel stupid.

self.currentLayer = data.currentLayer;

Should be:

self.currentLayer(data.currentLayer);

It works now. Wohoo!

I'm slowly getting there. I have added an event listner to add stats from the displaylayerprogress plugin. If installed, it will provide additional widgets for layer progress, height progress, average layer time and current fan speed.

Together with PrintTimeGenius, I now have the data I want and the layout is at least okayish.

1 Like

Just make sure that if you hover over one of the values or icons that you get a title that indicates what each is. Although I can tell that there are four time-based items, I can't easily guess what they are.

1 Like

Thanks for the feedback. I agree and it is already implemented.
I think the icons needs some rework to be more easily recognisable.

Good work!!!

I made the same mistake...several times :wink:
The first steps in Octoprint-Plugin Development is not easy because you need to learn a lot of diffenrent frameworks.

Thanks! This has certainly been a challenge. I've never written a line of js before and barely heard about the frameworks used by OctoPrint.

There's still a lot of things left before this can be considered a polished plugin ready for release but right now it actually does all I personally need it to do so that is a milestone.

2 Likes

No doubt. I had never heard of knockout before I started plugin development and I still to this day struggle with how the bindings work.

Open source is a smörgåsbord (so many frameworks, so little time...)