Enabling remote OctoPrint (HACs)

Annnnd, by the end of the day, getting M2000 S200 to work is pretty easy.

Also managed to get it to recognize filament runout while printing. There’s a filament_check command in print control that seems to now be unused? Added some logic to the J1 loop to get it to check again.

However, never quite found a good solution for syncing up Octoprint to the internal printing state. Almost kind of got it to stop moving when the filament ran out, but then it… went crazy. :sweat_smile:

So far I’ve found no way to trigger any HMI responses anyway. Even if it paused, and octoprint waited, there’s nothing showing on the HMI to tell it to resume when complete.

Boy, I bet this would be easier for a Snapmaker dev who has all the documentation for this and the screen firmware. :eyes:

Might be a bit late for this information, but back when I played around with the original controller/display and tried to extract as much as possible that I could use for the Duet, my impression was that the display acts similar as the old Repetierhost software which simply sends gcode commands to the Marlin board (in case of Repetier via USB, in this case it seems to be I2C or something?) and interprets whatever answer Marlin gives. As far as I could tell the controller board does not seem to be able to send anything to the display by itself.

This would also explain why

  • all the “special” commands such as M2000 and such work(-ed, can’t speak for current firmware versions) nicely when sent via terminal if you connect a computer with Repetierhost to the back USB
  • you did (and probably still do) experience similar hiccups as Repetierhost suffered all the years with the J1 display if you enter commands too fast or in a sequence Snapmaker has not thought of before.
2 Likes

No, that’s still interesting. Thank you. Though I guess, yes, the screen is not just a simple USB connection. You might know better if USB or I2C from dissecting it?

Somehow I guess that the screen is still listening since it does subscribe to status from the printer. In a few of my attempts, I noticed the screensaver was showing the print time of the short job I had completed, even though it never showed the active print status screen.

And yes, I notice quite a lot of debugging code dedicated to monitoring the rate of print commands.

Before I look a little more this morning, I’ll try to summarize some general observations after giving up yesterday. :sweat_smile: One is regarding the power loss hardware you noticed, @Mechanikus.

First I need to rant: One of my big pet peeves I see repeated over and over again is the duplication of effort in software. Someone creates something that works relatively well, then someone comes along and essentially ignores all of that code because they want to do it “better.” It’s very frustrating and in the context of Marlin just fractures the code. :triumph:

There are features I see in this repo that might have been minor additions or modifications to base Marlin that instead got replaced or partly duplicated which help to understand when editing.

For the interested, these include:

  • Job queuing and print status (see print_control.cpp, system.cpp, J1.cpp)
  • Filament runout detection (see Snapmaker’s filament_runout.cpp and PrintControl::filament_check)
  • Power loss recovery (see the snapmaker power_loss.cpp)

The last two of these are owing to the hardware @Mechanikus spotted in his teardown.

Filament runout: The filament runout customization is due to the analog encoder on the filament runout. None of the standard Marlin filament check code is used. Instead a count stepper runs on the Snapmaker runout sensor, which is periodically polled while printing.

But where? It appears this isn’t interlocked on the main board side anymore. At least the code for it is there, but it seems like code to poll it is commented out? Perhaps it’s now periodically monitored by the screen under normal printing from the screen. In any case, polling at the main board is easily re-implemented if needed.

I got my own polling going by summoning PrintControl::filament_check() from PrintControl::loop called by the J1 timer, when the system status is “printing.” It wasn’t obviously called anywhere else otherwise, though it’s possible I missed it.

Power loss recovery: I have not looked deeply into this yet, but I think is why I didn’t get M600 working. (M600 checks and stashes the current file location through power_loss.) I did initialize power_loss, but pretty sure I didn’t maintain it properly. This also led to it falsely offering to recover every time I restarted the printer. :sweat_smile:

Power loss recovery also has its own entire, custom implementation. Again, this includes special hardware. As Mechanikus noted, there’s a special 5V board inside the printer that briefly holds some power in the event power is lost. It’s used to buffer the current print status in the event of a failure.

A whole structure including the print file, line number, some printing status, etc., is maintained line by line during printing. And needs clearing upon completion of a print job, surely. On restart this structure is apparently checked, and offered to resume if needed.

Job queuing: Holy heck… So I haven’t looked too carefully at this yet either. But there is the SACP (Snapmaker action command protocol) that for all intents and purposes seems to go one way: From the screen to the printer.

The exception may be to send a print job to the screen. Looking at sm2uploader (for example), you can create a package that includes the gcode to print and maybe deploy it for printing from the screen. There may be some code in the firmware for transmitting these also? Either way, the screen would then take over issuing gcode lines and all the job management works as intended.

Within the printer firmware, there are whole other layers of abstraction for keeping track of the print job and status besides the Marlin planner, etc.

print_control and various events in the Snapmaker libraries can trigger certain things, with status kept track of in system. The J1 function has a periodically summoned timer on each loop which also calls some print_control stuff, and logging (which is quite annoying, and delightful to push from LOG_I to LOG_V).

Status may be monitored by the screen. But the screen seems to expect it’s in charge of issuing commands line by line during a job. So my best guess is that a lot of additions to print_control and system (set_ and get_status) are needed if one wishes to introduce a third-party host like OctoPrint into the mix to avoid any possible conflicts.

Importantly one probably also needs to keep power_loss up to date, and I’m not yet sure how or if that’s possible if you don’t transmit the entire print file to the screen to be stashed for recovery. (OctoPrint issues code line-by-line, with an option to send to SD card. I don’t believe the power_loss cache or the internal screen memory behave using the standard Marlin SD card library, though. And anyway, the data would need to be packed into an SACP stash probably to be recognized.)

On M600: While I haven’t quite triggered this right to manage the power_loss structure, I’m not sure filament runout or M600 will ever actually trigger the event on the screen if the screen isn’t in a mode where it’s executing the job. So… how will you then trigger a Resume command when finished…? It might happen, I just haven’t seen it.

So… there’s some broad observations for anyone else that looks at this stuff. I’m not sure I’m seeing “the forest through the trees.” Also not sure why I’m still picking at this. :sweat_smile: Guess it’s just interesting, and would be nice if the board could obey a third party cleanly.

1 Like

For runout it’s necessary to resume on octoprint if the job is executed there.

I did this by frequently asking for the endstop status (M412 or M119 if I remember correctly) and searching the terminal for filemant (note the typo here :wink:).
Unfortunately this caused stuttering even if asked for it every layer…

For the J1, there’s no apparent stuttering if you add logic to check the sensor status once in a while. I was then trying to get it to emit //action:pause to see if OctoPrint picks it up automatically by HAC.

Ran into some other issue though… I think I didn’t do it right.

I did not take a deeper look at display communication, but when I dismantled the thing, @Miq19 suspected that the display talked via I2C to the main controller.

Regarding printing and data transfer, my (blind) guess was the following:

  • any print file sent to the printer vie Wifi or copied from USB is stored on the display - since the display is the Wifi endpoint and also the point where the front USB ports end, the main controller is not involved in this at all.
  • when you print something, you start the job via the display, so all the display needs to do is read out the print file and send it line by line to the Marlin controller, wait for OK, send the next line, etc. until the file is finished - again, identical to what a PC with Repetierhost does for a printer that is connected via USB. (The drawbacks of that are by the way the reason why modern printers do not use Repetierhost as they used to in Printrboard times - the second the operating system decides to put the USB port to sleep you will get a print failure…)
  • By doing so, storing / calculating the estimated print progress is also obvious: that happens on the display alone, without any feedback from the controller necessary. The only thing you need is some message when power breaks down in order to store the line you are in persistently - which is the 5V signal from the “power loss” board. Therefore I would suspect that this signal is routed directly to the display - if it does anything on the Marlin controller it only needs to trigger some kind of emergency stop / all power off mechanism in order to let the display finish its task instead of drawing the rest of power into steppers and heaters.
1 Like

Unfortunately this isn’t entirely true. How it streams data from the HMI screen to the mainboard is not as simple as base Marlin gcode interpretation, as far as I can tell. There’s additional business logic for input and control from the screen.

Emulating all of it so that gcode received directly from the rear USB behaves identically seems not so easy then. A lot just works, but some important things do not. :confused:

1 Like

That might well be indeed! You are much deeper into that topic that I have ever been, since I very quickly decided the mess of programming you are wading through right now is something I really do not want to fuss with :wink: and concentrated on how to get the data I needed. That alone showed me quite exactly what you found since quite some of the Marlin settings are set - only to be disabled and replaced by something buried somewhere else later :dizzy_face:

That was another one of the reasons for my “simply rip anything containing Snapmaker software out” solution.

1 Like

Chatting with @nivekmai about the SM2 code, there’s some differences in communication handling on the J1. There’s a seemingly more substantial event system (snapmaker/event).

For example, this should be sending a gcode-issued pause command when set_status is used. Terminal output shows it’s triggering the event_printer code to do a send_event via pausing_status_deal, but the reply suggests it’s not the M600 type it’s sending, which is strange.

I triggered that many times and it just reported “system puase done:0” [sic] instead of the expected “M600 pause done”. :man_shrugging: It certainly didn’t pause though. Or it did, and since OP was still chugging away, it plowed right through it.

(For some reason I can’t get host_action to send an echo on pause?)

Yea, the j1 firmware is totally different than the sm2 firmware. Looks like it’s barely even Marlin anymore.

Does M600 perform as expected if sent via octoprint, while printing via octoprint?

Rather than trying to get Octoprint and the Android screen to work well together, I am taking the route of eventually abandoning the Android screen completely and finding ways to achieve all of the same functionality through Octoprint and regular Marlin gcode.

I can do without the Snapmaker custom gcode for fast tool change because using normal Marlin gcode to make one extruder park before the other extruder moves only takes 2-3 seconds longer. Not a big deal to me.

Power loss recovery can be achieved with an Octoprint plugin

Advanced pause feature can be executed via Octoprint (unless snapmakers modification to the code related to M600 has made it completely dependent on the android).

Filament runout can be handled by more common runout sensors connected to Pi and utilizing an Octoprint plugin.

All that’s left are the offset and bed leveling calibration sequences that we get with the Android screen. Right?
I haven’t dived very deep into this yet because I’m focusing on hardware changes at the moment, but I bet the functionality of these could be replicated with normal Marlin gcode and Octoprint.

Xyz offsets can probably be handled with G425 using the conductive square in the PCB, and using probe_top_at_edge for z. Might need some creative settings or a little modification to the underlying code, but shouldn’t be that difficult.

The bed leveling is a tough one. You’ll have to level the bed manually, which is annoying and slow, but this is not something that needs to be done often anyway. Especially if you use silicone spacers to replace your bed springs.

This seems the best route to me if you are like me and cannot live without Octoprint, and also just want one screen to handle everything (Pi screen with Octodash). Alternatively, you could just keep two screens on your printer. One on your Pi running Octodash for everything print related, and your Snapmaker Android just for the calibration sequences.

As written it does not, no, described above.

Those are all fine options as well. But at that point I’d go back to considering trying to compile Klipper. The solution I’m chasing above is with hopes the screen and Octoprint could just play nice in a way that’s easily shared.

If you want to go that route though, there’s enough code in the Snapmaker Marlin source to integrate the runout sensors with base Marlin. I pretty easily got those to trigger regardless of the screen, just the usual Marlin response is not currently used.

They’re kind of better than binary logic runout sensors since they also detect jams.

Just an update that I, for one, am putting this on hold indefinitely.

I’ve learned a lot about how the Marlin firmware works, and there’s a handful of little things I got to work.

Like reading the filament sensor (which could be made to trigger an OctoPrint event if needed, probably easily) and faking out the built in fast tool change code to work remotely (not fully tested).

However, even after a first look through the event system code, it isn’t obvious to me there’s any way to trigger UI events on the HMI/screen from the main board/octoprint. For jobs not initiated by the screen, that’s a big bummer if true.

If the filament runs out or the you do an M600 command, or you’re at the printer and just want to manually pause it or make an adjustment, I’m not sure that’s possible without needing a second screen. I had some other ideas to try, but that means probably another dozen trial and error firmware flashes hunting in the dark.

I took apart the screen apk code, but honestly I’m not learned enough to follow the dense and broken java that spits out. So what the screen is and isn’t doing is totally lost on me, left just probing it for responses.

I did see one sign I got it listening kind of. The screen saver showing some status of a job started by octoprint. But no other signs on any interactable screens.

All to say, unless I get a nudge in the right direction, I’m moving on for now!

One concrete benefit came from it: I found and turned off the constant status message spam the J1 barfs out over the terminal. :joy: