Could this become a plugin?

Sorry for this title, but the question is too complicated to fit...

I'm developping a Marlin Mode for OctoPrint : it needs some external hardware (a STM32). The STM32 spies the printer motherboard SPI 128x64 display output, and builds a bitmap. When the bitmap is ready, it sends a signal to the RasPi GPIO. In turn, the RasPi opens its SPI master and downloads the bitmap. It then resizes the bitmap and displays it on the printer's LCD touchscreen, using the frame buffer (support for 32, 24, 16-565 and palletized 8 bit/pixel). With the RasPi SPI master @ 8MHz, 7x scaling (a 1bit pixel becomes 7x7) and 32bpp, the lag is less than 25ms (from Marlin begining to spit data to the image being 100% displayed on the RasPi LCD). A push button allows the user (me...) to toggle Marlin UI / OctoPrint UI (or any wrapper as OctoDash or whatever).
Had to use a STM32 because SPI slave mode is not supported by the RasPi (a shame !).
Very little resources usage (interrupt driven, and multithreading) : less than 1% CPU usage when displaying MarlinUI, 0% when not (measured with top).
I did this because I'm building a OctoPrint 3D printer control box, and wanted to get rid of the Marlin display : wanted Marlin and OctoPrint on the same display, and just keep the rotary encoder.
Until now, nothing is really related to OctoPrint. It works for any software using a HDMI display... It also works over VNC (assuming "CaptuteTech" is set to "raspi" in vncserver-x11 file).
It is very different from the SKR MarlinMode (a real emulator), but the result is the same, except it is for any printer software running on a Pi, with a local HDMI display (have no SPI TFT for testing...).

I'd like :

  • the RasPi bare metal daemon to be compiled and installed by installing a plugin, so there's no need to SSH commands

  • some parameters to be adjusted from OctoPrint : foreground/background colors, pixel multiplying (scaling) and SPI speed

All low level and OS operations.

I'm pretty sure the parameters are not a problem : just reading/writing an etc file

But installing a daemon or a service ?

Is this doable ? Most of all, I've no idea where to start from. And I'm not used to high level langages (and don't really like them...). Have a hard time with OctoPrint Hello World !

Any advice ?

If you can call these things from Python then the answer is yes. Rule of thumb for registering on the plugin repository, it shouldn't install things on the system without explicit permission. Python supports C style extensions, that could be an option. I know Arc Welder and OctoLapse plugins both use this. Most things can be done, if they can at least be called from python (even if it is command line, using subprocess or somthing).

Sounds like a cool idea, I would recommend doing some more basic Python tutorials first before the hello world plugin tutorial, so you get an understanding of the language. How OctoPrint fits together with Python server, and the HTML/CSS/JS frontend powered by KnockoutJS can take a significant amount of getting used to.

Sorry for the late answer : one disk from my RAID array died on me, and I'm currently rebuilding 7TB data...

Now some flooding ! Be warned !

It's interesting you're talking about OctoLapse : I started with Python and Bash because of it, in order to get a GoPro working with OctoLapse throught gopro-py-api (by KonradIT) (my tiny scripts : GitHub - yet-another-average-joe/OctoLapse-GoPro: [OBSOLETE] Experimental ! How to use a GoPro with OctoLapse. Tested with H3+BE only.) ; with help from FormerLurker, it needed just a couple of days. Bash is easy. Python is disturbing because high level and indentation based. But powerfull. These two languages are so common that there's always a tutorial and some snippets that can be used as a starting point. In the end, procedural languages all work the same : algorithms are algorithms.

It's also because of OctoLapse and its calls to OS that I thought maybe OctoPrint could be and option for the parameters (and reading the APIs seemed to confirm the idea).

The problem is not really procedural things. It's everything that's webserver related. For my - more or less abandonned - website, I used DreamWeaver in "designer mode", meaning I very rarely had to look into HTML and CSS. It's not currently installed on the PC.

I don't understand OctoPrint's structure. From my point of view, web design is not high level, but stratospheric.

What could I use or read that could give a global view of OctoPrint, with it's files dependencies and so on ? Could Dreamweaver be of any help ? I didn't find any index.html or index.htm , and typing it gives a not found error.

Is there some IDE that fits OctoPrint so I can first see the things from top to bottom ? Can't believe web devs type only HTML lines of code (even if YT and TV are full of such examples)

(adding my app to the repository is a bit optimistic ! And anyway, it needs some wiring, PCB etching, some Marlin tweaking, and flashing a STM32 : someone that does that can type a couple of very simple commands in a terminal : it's more for the challenge, like OctoLapse and the GoPro)

For OctoPrint, the HTML code is created using Jinja templates, served from a templates folder. This constructs the UI inserting all of the plugin's templates in the right places etc. etc. There is no use of some sort of design software, the vast majority is hand typed with templates etc. making some of the hard work easier.

The core index file is under octoprint/templates/index.jinja2 ,the rest of them are in the folders above there. Each bundled plugin also has it's own templates folder, for injecting additional things. All the other web assets are in the static folders, these are registered by Flask (the server framework) so that they can be found on the server.

If 'DreamWeaver' generates HTML code, then sure you can generate things to go in plugins quite easily.

The plugin tutorial is a good place to start, since it takes you through just a server side plugin, then adding a UI to make it do stuff. It starts with a simple 'Hello World' log output, all the way up to a component in the navbar I believe, with some settings.

Thanks ! With the information you provided, I found tutorials written in my native language.

Yes, DreamWeaver generates HTML and CSS with a WYSIWYG interface, shows and synchs a website with its hierarchy, locally and remotely. After some reading about Flask, I doubt it could be usefull. I will not reinstall it (except in case I feel the need to work again on my old website).

I will update this thread when I get some results. Probably with a link to a short video showing things in action.

Some progress : a Settings page.

MarlinUISettings

For now, no data bindings, no code. Just html.
I was wondering if the framework coulkd allow for displaying a preview image of the Marlin Ui on the bottom right corner, facing the colors combo boxes... On my side, the problem is at HTML/Jinja level, generating the bitmap is easy.
Just need a yes/no answer...
(HTML is definitely not my cup of tea, but only the results matter ; to be honest, I'd like the answer to be 'no !', but if it is doable, it has to be done)

It's open ended so you can pretty much do anything you can come up with and program. I know the DisplayLayerProgress plugin kind of emulates the look of the control panel for M117 pop up messages that it uses for parsing.

Thanks ! This plugin is not installed because there was some problems with OctoLapse, and since then, never installed it again. But I will give it a try and have a look to the code.
Your plugin "Custom Background" was my starting point for combo boxes.
About combo boxes, there's a strange behaviour : when closing a plugin settings dialog, the combo boxes data are saved to config.yaml, whenever they are set to default values.
Is this the expected behaviour ? Shouldn't keys with default values be discarded from config.yaml ? It seems to me it's also true for numeric values : they persist even when defaults. Booleans and strings are discarded in such conditions. Or maybe it's some bug in my call to get_settings_defaults() (but everything works fine)... This behaviour, if it is a feature, will make my life simpler.

Now writing a parser so the low level app can get its parameters from config.yaml... Back to C++ for some time.

I've also been looking at forcing all settings to be saved to config.yaml, (on_settings_save() function), but coudn't find any example in the repositories (saving the whole disctionary)...

I think it will depend on how you are binding your select options. There are a couple of ways to do it, using an internal observable, or via settings observables and get_settings_defaults. That being said, it might be easier to understand what you mean if the code was available on github or gitlab to look at how you are doing the bindings, etc.

It's binded the simplest possible way ! It works as I'd like it to work : writing eveything into config.yaml . It would be even better if it wrote defaults to config.yaml at startup. Also needs some sanity check : as is, the same pin could be selected for SPI interface and button, or same color for foreground and background.

[EDIT] not sure someone could understand what I'm doing. See here : Marlin Mode for OctoPrint TouchUI (work in progress) - YouTube

I think there is a way to write the settings to config.yaml using self._settings.set with a force=True, but you'd have to work out the logic to do it only once. You might be able to do that with overriding get_settings_version and on_settings_migrate. This is one of the things that I use for updating settings changes between versions.

Something like this I think would work....

	def get_settings_version(self):
		return 1

	def on_settings_migrate(self, target, current=None):
		if current is None:
			default_settings = self.get_settings_defaults()
			for key, value in default_settings.items():
				self._settings.set(key, value, force=True)

I'm not sure if you need a self._settings.save() after that for loop or not.

Instead of writing to config.yaml, you could also create your own data file (which would go under ~/.octoprint/data/my_awesome_plugin/), you would then have full control over the schema etc that it is expecting there, and how/when it wrote to it.

Unfortunately, as soon as get_settings_version() is added, the plugin is flagged incompatible in the log file, and does not display. Will see why later, as there's still a lot of work work to do on the low level app (logfile code still to be writen, and learning how to use systemd in order to make it run as a service : really started with Linux 2 months ago).

About the persistent data, config.yaml does the job for now. For default data that can't be saved, the workaround is using a second set of default data in the low level code. Not great, but acceptable for a dev version. The parser assumes all data are in config.yaml, with no missing values management ; adding defaults in the C++ code is no big deal.

The plugin now tells the low level app how to behave, and I'm super happy ! Changing colors, pins, SPI speed on the fly without recompiling is great !

[EDIT] running it as a service was super easy. When flippling the power on, Marlin Ui appears on the LCD after 20 secs. Some cool functionalities I've been thinking of for a while :

  • detecting when the underlying app is active, and display it
  • switching between different UIs, using hardware buttons : OctoPrint, OctoPrint + TouchUI, OctoDash, OctoScreen, etc. No idea how to do that (and they all use different window managers)
    (obviously, everything configurable using the plugin)

Until now, I failed at getting access to the window managers, except when working from a desktop environment (Pixel). It's ennoying, because all HID events go to the underlaying app, and the low level can't grab them in order to prevent unwanted actions.

I think the issue is probably indentation formatting. I was able to get it to load but unfortunately, I don't think my approach works.

@jneilliii : don't spend time on my project (at least for now !) ; I edited my post and didn't see yours (didn't get the notification). I'm now back to low level coding, and will be soon be on hardware design, 3D printing and machining. Will come back on the plugin project at the end, as it is the highest level. The skeleton is functionnal, and it is great. Many thanks to you all !

[EDIT] RaspBian is amazing !
If the low level app is ran as a service, it seems it's unable to paint on the framebuffer's 2nd page ! It's all messed up by OctoPrint or TouchUI. Maybe xorg doesn't understand what's happening. Normally, X paints on the "normal" GPU 1st page, and the (my) low level app on a 2nd one, so the X apps can't show up underneath. They do ! This is crazy. Running my Marlin UI as a service and booting to Chromium/OctoPrint gives a messed up screen (messed up by everything that's updating OctoPrint display). Running it as a daemon or as a normal app, after X/OctoPrint is already on screen, and everything's fine. Go figure... Not a fail, but another problem to solve. And the wife is asking for the class D TV amp to be completed...

1 Like

@jneilliii When I installed your Custom Background plugin a few weeks ago, I have been thinking of displaying the Marlin Ui bitmap as a bitmap in the temperature graphics. But immediately gave up.

But...

Windows offers "memory files". I searched about such a feature with Linux (no reason it does not exist...), and found tmpfs. I'm pretty sure it could be done : would require a thread that refreshes the display with a memory file bitmap = a slideshow with one picture. I think I could write a C++ testing software that generates a changing file (in order to avoid all the needed hardware and software I'm working on). In Custom Background, it would require the implementation of a slideshow feature, and a path to a Linux folder. Could you be interested ?

Sounds like basically the way the mjpgstreamer works with an image tag. I personally would not be interested in implementing that in Custom Background but I would point you in the direction of the snap stream plugin for a potential method of "refreshing the image url". You could implement your own routes hook to a temp file (or your plugin's data folder) the way that I do with Custom Background, and with that js side of snap stream have it cycle through and change those periodically.

There's a more efficient way to do this : Linux inotify mechanism ; just discovered it searching about an equivalent to some Windows APIs ; ) : handler/hook called when the OS detects a file was modified. The feature that makes (for example) a text editor detect that what's it's displaying was edited in it's back by some other software. I'm playing with it (in C...) about /dev/input in order to detect HID actions and close the frame buffer display when the user touches the LCD (or hits a key on the keyboard or whatever).
Seems to be in Pyhton language : inotify Β· PyPI
I think I will give it a try when I'm more advanced with Python.
I already had a look to snapstream, it plays with video streams and jpegs. (seems overcomplicated to me)

Just a follow up : this is what it could look like :

Warning ! This image is a fake ! It is not in the temperature subwindow. It is an overlay, using "dispmanx" (no Photoshop !). Scrolling the browser window, the Marlin UI stays at the same place.

I gave up with the Linux framebuffer (not enough features). There's some software that comes with OctoPi, in the root/opt folder. Undocumented, but with some examples. These libraries give direct access to the GPU (among other things).
Half the code amount, many more capabilities (displaying Marlin or anything else while booting), and half the CPU usage (dropped down to 0.3%).

2 Likes