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.
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.
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.
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. 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.
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.
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. Guess it’s just interesting, and would be nice if the board could obey a third party cleanly.
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 ).
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.
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.
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 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
That was another one of the reasons for my “simply rip anything containing Snapmaker software out” solution.
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”. 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?)
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.
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.
For @DLMCW that might be useful for others. It’s been a while since I looked at this, so please thoroughly test yourself and make sure that your changes compile cleanly and without warnings.
Quiet terminal spam
In snapmaker\J1\J1.cpp, lines 461 and 465 can be changed from LOG_I to LOG_V so that it only appears when viewing verbose logging. These lines otherwise spam the serial output every minute or so and can clutter the OctoPrint terminal. Likewise lines 321 and 322 from LOG_E to LOG_V if you like though these don’t come up as often.
Fast tool change (M2000) for OctoPrint (needs more testing…) Possible solution, with huge caveat. In snapmaker\gcode\contorl\M2000.cpp comment out lines 247-250 re: check printer status.
Note that “printer status” is as defined by the SAPC, meaning prints initiated by the screen, or at all, really. Triggering via any M2000 command may result in collisions.
This disables the check for the printer status = printing which as risks to be aware of. For example, a user might accidentally request a fast tool change while the extruders are parked.
It also checks the print mode that’s set to make sure it’s not in duplication mode. It’s feasible that an OP-initiated print always registers as “NOT duplication mode,” so there may be no protection from accidentally trying to do a fast tool change while in copy/mirror mode.
Also, at some point I thought Snapmaker’s custom fast tool change code appeared to leverage the custom power loss recovery system to aid in a tool change. But I think I tested this change and it seemed to just work on a simple, two-color print most of the time.
Summary: This is mostly dangerous because even if it works, it bypasses an important check that could be a problem if anything asks for M2000 S200 when it’s not actually printing.
Filament runout detection I was never able to get a filament loading UI to display on the screen, and a comment I got from Snapmaker leads me to believe this is not possible! As described above, this is why I abandoned working on this further, as it would require some other process manage what happens on runout of an OctoPrint-initiated print.
Reminder/background: The J1 does not use a standard logical filament runout sensor. It instead has a rotary encoder to detect filament movement through the extruder which is tracked by custom Snapmaker code in the Marlin base firmware. That custom code is complicated, but also it’s not difficult to call the Snapmaker functions for it. They’re just not implemented in places the standard Marlin definitions would see it (yet).
This means simply enabling HAS_FILAMENT_SENSOR in the standard Marlin code will not, by itself, enable filament runout detection or jams.
If you want to try anyway… It helps to understand that the firmware on the printer runs a whole parallel “event system” and print job manager that keeps it synchronized with jobs initiated by the printer screen. It also has a highly custom and robust power loss recovery system which is also managed by the print control and event system.
The functions in snapmaker\module\print_control.cpp and system.cpp hold the Snapmaker libraries/functions for setting or checking the printer settings. They use the event system code defined in snapmaker\event\ for communicating events to/from the HMI. Functions for print control and system status set and get flags on the print job status, and in places may communicate with the HMI via the event system. Commands sent to the HMI are only received if the HMI is listening, which only happens if the HMI initiated the print job.
That out of the way, for filament runout specifically:
Unfortunately whatever happened, I reverted these changes! Let’s try to make it happen again.
The code for the filament sensor are in snapmaker\module\filament_sensor.cpp. You can see an example of how it works with the M412 standard gcode in Marlin\src\gcode\feature\runout\M412.cpp.
The filament runout detection needs to be initiated using filament_sensor.init(). This happens automatically if a job comes from the screen. For prints that come from OctoPrint, I had been adding extra code to M110.cpp since OctoPrint jobs regularly update what line they’re on. So if the line number was zero, I ran code here to initiate things. Including starting the filament sensor, like:
if (parser.value_long() == 0) {
LOG_I("----- Attempting to start filament sensor\r\n");
filament_sensor.init();
}
Looking at my change history, I also started the Snapmaker job planner and started print_control in an attempt to initialize the whole Snapmaker event system [planner.synchronize() and print_control.start()].
Then PrintControl::filament_check() can be used to check the filament sensor if it’s been initiated. I think I added code in filament_check to blast a message that a filament runout was detected.
Note print_control.loop() is called on every iteration of the while loop in J1.cpp. So in PrintControl::loop() for the section where the system status is SYSTEM_STATUE_PRINTING, I added a line for filament_check() at this point.
Because I had added a print_control.start() in my M110 code above, my system status per the print control logic was indeed printing.
This is all pretty incomplete, but I was able to have filament_check spew a message to the terminal when the filament sensor ran out. And somehow I had triggered a Marlin print pause, but I don’t have a history of those changes!
A better implementation would start the filament sensor when a job starts via OP, turn it off when done, and also handle telling the OP host to PAUSE and provide the interface for load/unload, since the screen cannot be triggered to do so in that case.
It sounds complicated, but the code to actually turn on/off the filament sensor and check its status is actually pretty simple. It’s figuring out good places to put those lines, then assist with intervention is the part I stopped thinking about when I realized it could not be done via the screen for an OP job.
I do also recall, while trying to get this to work, I might have noticed the Snapmaker custom code for print pauses uses the power loss recovery system to make a note of where the print is before it parks the extruder. Then uses it again to restore the motion where it left off once the user is done interacting with the screen to load the filament (recevies a “resume” from the HMI).
So whatever happened, I think when I got it to use Marlin to pause instead, the position of the extruder got kinda messed up and I just stopped.
Using power loss recovery is a strange and non-standard way to note the position before a pause. But I guess it’s also kind of clever since if the user never shows up, power loss recovery will save the print assuming the bed doesn’t cool off.
I’m almost there but still needs testing. Thought I would post the repo in case anyone wants to help:
I’ve got host action commands and prompts working with Octoprint via OP web interface. I will try to see if I can use the Snapmaker screen for anything later but first I want to get runout ironed out, and I am content to use the Octoprint web interface and/or my RPi screen to handle runout.
Prints from OP poll the runout sensors using Filament_Sensor.check(), via Marlincore idle loop where runout.run would usually be. When filament_sensor.check returns is_trigger, it calls runout.cpp’s FilamentMonitor::runout_detected so that the rest can be handled by normal Marlin code/files.
When this happens, the firmware, via runout.cpp, directly injects M600 into the queue.
M600 pauses (OP receives //action:pause command), parks extruder, and sends a host action prompt telling you to insert filament. It enters a heater timeout loop at this time and waits for you to click continue. After you hit continue, it goes through the configured advanced_pause sequence and resumes the print.
That’s how it “should” work based on the code changes. My last test failed to park nozzle and I had glitches because I had duplicate pauses happening simultaneously and I also forgot to enable park_on_pause. I believe I’ve fixed all that with the last commit but I haven’t had a chance to test it again yet. Will update when I get a chance to test it again, but I’m optimistic. Host action commands are awesome.
I haven’t looked, but sounds exciting. Not a small amount of work!
You’re using the base Marlin side to manage parking, etc? Do you check if the HMI is in control before deciding if to use your custom Marlin/OctoPrint park code or the default code? (If not, I could give a couple of lines to check if the print is being executed by the HMI to help decide and avoid conflicts. You can do a basic system status check, and see if it’s running a screen job, depending on how you have this set up.)
Regarding the built in screen, I would add:
I know I already said it too many times, but I spent a lot of time trying to trigger the unmodified HMI, and based on a short note from Snapmaker devs, confirmed this is very likely not possible.
You might mean modifying the screen or replacing its software wholesale?
If #2, this might be possible though a big job. We know from other threads how to play around with the Android screen a little. I’d hate to lose the calibration routines they already have and need to re-engineer them. There’s some ideas on how to repurpose the screen though. I haven’t seen a good flashing tool to really unlock the screen, only do high-level Android stuff.