Introduction


In our Software Engineering class, we were given the freedom of choosing an open source project to contribute to. I had became fairly obsessed with 3D printers over the past year and in turn I was familiar with one of the options, OctoPrint. OctoPrint is an open source host software that uses a web interface to allow users to control their 3D printer(s). 

OctoPrint falls under the Model-View-Controller (MVC) software architectural pattern. This pattern is common for web applications, and has 3 main parts that work together to allow the user to control and interact with the web interface. The model is the core part of this architectural design that stores the data that is entered via the controller and the model in turn updates the viewer. The controller is the component that actually allows the user to manipulate the web interface (through buttons and text boxes, etc.). The view is the display that reacts to the model updating, and is what the user is able to see. For example, if the user sets the temperature to the hotend, this action will manipulate the model. The model in turn will update the temperature graph that gets viewed by the user. 

 

This means that in order to contribute to the project we had to understand exactly how OctoPrint receives and processes information. We began by completing the Hello World! plugin tutorial. We ran into several difficulties within the tutorial, the main one being a problem with Cookiecutter, the plugin template software that OctoPrint uses. Once successful, we were ready to move on to an actual issue.

The Problem


problem

When exploring OctoPrint’s issue tracker, we found a request  for a safety timeout feature. The need for this feature was twofold. It was first suggested as a way to turn off the temperature after a print, but the need was also stumbled upon when an OctoPrint user had turned on the temperature to his hotend in order to clean it. He then became distracted, and with no safety feature in place, the temperature to his hotend was left on for over 2 days.

This feature needed to be implemented via an optional plugin. It would allow users to set a time  (the default would be at around 10 minutes) and would begin its job if the time and temperature of either the bed or the hotend were set (above 0). At this point we would activate a timer (with the specified time) that would check and see if the printer was printing once the time was up. If it was printing we did not want to mess with the operation, and instead would reset the timer to check again. However, if the machine was not in an active print, we would set all the possible temperatures to 0 (off). 

The Designs


Our design has drastically changed throughout the semester but the main concepts have remained constant. There were 3 main design periods that we went through that deferred from one another. Design 1.0 was the first brainstorm that we had completed before we had dove into OctoPrint, or experienced the result of the Hello World! plugin. Design 2.0 was what we had decided to implement as a result of our first design. However, this design had some issues that needed to be resolved, and so Design 3.0 is our current plugin implementation.

Design 1.0:

This design and those that follow are composed of 3 main design sections. The Timer User Interface is what allows users to specify the amount of time before the machine checks the machine state to attempt shutdown. The Timer is the back-end implementation of the timer that actually counts down for the designated amount of time. The Temperature Shutoff is the part of the plugin that shuts down the temperature if certain criteria are met. I began trying to work through Design 1.0 in this blog.

  1. Timer User Interface:   

    softeng

    Type:

    This would be a front-end implemented check box and a corresponding front-end input/alert box. It would allow the user to enter the time in minutes. We would need to set a default time in the back-end and grab the most up-to-date time before setting a timer. 

    Requirements:

    • Input box must only allow users to submit valid integer values (measured in minutes).
    • The input box should have text directing the user on what to enter. For example “Time in Minutes:” should be next to the box.
    • The UI must seamlessly integrate with previously committed features.
    • The check box must maintain its state until changed by the user.
    • The feature should default to inactive safety timeout. 

     Description:

    Octoprint utilizes a tab for settings which includes a section for optional features (shown in the figure above). Within this section we plan to implement a checkbox for the user to activate/deactivate the safety timeout feature. Once this feature is toggled on an alert box will be displayed for the user to input the desired amount of time for the safety timeout.

  2. Timer:

    Type:

    This would be implemented as a back-end Timer event that gets set off when the user enters a time. It would be set to the value grabbed from the front-end Timer UI. It would call a timer that basically counts down in the background and calls another event when finished.

     Requirements:

    • Timer event should not be called when check box for feature is toggled off
    • Timer event should implement some sort of backend timer to wait for the specified time
    • The Timer should return a boolean value when finished (if the state is in standby for the duration of the timer) in order to call next event
    • Recall itself constantly to check state of printer, only if safety timeout feature is toggled on

     

    Description:

    The purpose this event is to check the state of the printer for an “operational” state. The operational state is comparable to a standby state. If the printer is in this operational state for a time duration equal to the user’s declared safety timeout input, then the timeout should commence. The event described in this section, will continuously check the for this operational state.

  3. Temperature Shutoff

    Type:

    This will be another back-end implemented event that reacts when the Timer Event returns a Boolean value. The Temperature Shutoff will only be called if the Timer returns True (that the machine state has been only operational for the duration of the timer). The Temperature Shutoff will then kill the power to the hotend and the hotbed. 

    Requirements:

    • Temperature event should not be called when check box for feature is toggled off
    • It must only initialize when the timer has finished and the state of the machine has been “operational” during the timer countdown (Returns True)
    • This event should return a value to let us know it is working correctly/ to alert us of any exception handling
    • It should check and print out the temperature state of all components

     

    Description:

    This event will be implemented as a function that corresponds with the temperature actuators. We want to deactivate the temperature under the conditions that the machine is in the “operational” state and has been continuously in the operational state for the duration of the user declared timer. This will result in the temperatures of both the bed and the hotend to be set to off (0). 

Design 2.0:

This design is what happened based on our first design. We had completed the Hello World! plugin and were realizing some of the things that we did and did not need. We completely threw out the idea of a toggle check box that allowed the user to turn on/off the plugin. We realized that if they took the time to integrate the plugin, then they probably wanted it. Also, if they wanted it to be off, they can just set the time to 0. We also threw out the idea of the events, and simplified our life by using functions. The last main difference is that instead of having the timer check constantly if the printer stays in operational mode, we have it wait the duration of the time doing nothing, and then check to see if the machine is printing. If it is not printing, we move ahead with the Temperature Shutoff, else we restart the timer. Design 2.0 is described working for the first time in this blog.

There were a lot of problems wit this design that we did not take into account at the time. The main being that we used “while loops”. Since the server was responding to us, we did not realize that we were holding up other plugins on the event que by using the while loops. Another of our biggest struggles in this design was trying to figure out where to initiate the timer and how to have it work more than once.

  1. Timer User Interface:

    valid

    notvalid

    Type:

    This became a front-end implemented input box that allowed integer input from users. The default was set to 1 minute for testing. 

    Requirements:

    • Input box must only allow users to submit valid integer values (measured in minutes).
    • The input box should have text directing the user on what to enter. For example “Time in Minutes:” should be next to the box.
    • The UI must seamlessly integrate with previously committed features.

     Description:

    Octoprint utilizes a tab for settings which includes a section for optional features. Within this section we have created a tab for our own Safety Timeout plugin. Once the user clicks this safety timeout plugin, it brings up a page that has an input box that directs the user to input the time in minutes. The user can enter any valid integer.

  2. Timer:

    countdown

    Type:

    This was implemented as a back-end function called countdown within the init.py that took in the user-specified time and used the python time module to create the timer. The timer gets called in the on after startup function, which basically says that when the server starts up, we start the timer. 

     Requirements:

    • Timer grabbed most updated time
    • Translated the time in minutes to the time in seconds
    • Used a while loop to delay until the time was up
    • End when finished

     

    Description:

    The purpose this timer is solely to countdown for the time specified by the user. When the time is up, it should return to the previous function to call the temperature shutoff. 

  3. Temperature Shutoff

    shutdown

    Type:

    This is another back-end function that needs to be called after the timer has finished counting down.

    Requirements:

    • It should first check and see if the printer is printing
      • If the printer is printing, it should do nothing
      • If the printer is not printing, it should set the temperature of the tools to 0

    Description:

    This function will have two jobs, get the printer status and act on it. It should first check and see if the printer is printing.  We only want to deactivate the temperature under the condition that the machine is not in the “printing” state. If the machine is in the printing state, then we want to eventually reset the timer (call countdown), however, as we implemented it, it only printed a warning. 

Design 3.0:

This is our final design where we had to throw out all of our previously used while loops. We learned that while loops do not play nice and will halt the event cue. We instead learned how to use the Repeated Timers that OctoPrint offers. Repeated Timers are similar to while loops, but they play well with other plugins. This has helped us solve our previous problem of not knowing when to initiate our timer because it allowed us to use conditions about when the timer should start instead of starting it immediately upon server startup. The only thing that did not change was the Timer User Interface.

This new design has three new components, a settings save that allows us to change time, an Original Repeated Timer, and a Conditions function. The Original Repeated Timer calls the conditions function every second. The conditions function has a set of conditions that are trying to get passed. If they do get passed, we set another Repeated Timer (our actual timer) and have it call Shutdown when the time is up.

  1. Timer User Interface:   

    This design is the same as Design 2.0. 

  2. Original Repeated Timer:

     onstartup

    Type:

    This is implemented as a Repeated Timer that calls the conditions function every second.

     Requirements:

    • The Original Repeated Timer instance is initiated and started on server startup.
    • It should continue to check the conditions the entire time that OctoPrint is being ran
    • Set the run_first parameter to True because it can immediately start checking the conditions function

     

    Description:

    The purpose this repeated timer is to continually call the conditions function to see when to start our actual timer. All that this repeated timer does is call the conditions function every second after server start up. 

  3. Condition:

    conditions

    Type:

    This is a newly implemented back-end function that gets called by our repeated timer. It must pass a series of conditions to call our actual Timer, it will do nothing if it fails any condition. 

     Requirements:

    • Check to see if there is a time, if there is continue, if not return
    • Check to see if there is a temperature in the bed or the hotend, if there is continue, if not return
    • Check to make sure that another timer has not already been started (a boolean class variable that we toggle once we start the user-specified timer)
    • If it passes all of these conditions it will create a Repeated Timer instance with the amount of time that the user entered.
    • If it fails any of these tests, it does nothing as the Original Repeated Timer is constantly calling it.

     

    Description:

    The purpose this conditions function is to make sure that all of the conditions are met before we start our actual user-specified repeated timer. A timer should not be initiated unless both the time and temperature of one of the components are set above 0. It also should not create another repeated timer if there is already one instance running. 

  4. Timer:

    maketimernew

    Type:

    This is a back-end function that creates an instance of OctoPrint’s repeated timer class. It takes in the user-specified amount of time in seconds (we convert the time from minutes to seconds), and creates a timer based on the amount. Once the time is up, it calls the shutdown function, and then we cancel that instance of the timer. 

     Requirements:

    • All of the conditions were met in the conditions function
    • Time in minutes converted to seconds
    • We set the run_first parameter to false because we don’t want to call shutdown until the time is up. .

     

    Description:

    The purpose this timer is to wait the specified amount of time and then call the temperature shutoff function. 

  5. Temperature Shutoff

    lastshutdown

    Type:

    This is another back-end function that needs to be called after the timer has finished counting down.

    Requirements:

    • If we are in the temperature shutoff instance, we know that our user-specified timer has completed, so we can toggle the class variable (boolean) back to false to specify that there is not an instance of this timer running.
    • It should first check and see if the printer is printing
      • If the printer is printing, it should reset the initialstart boolean to True to allow the Conditions function to reset the timer
      • If the printer is not printing, it should set the temperature of the tools to 0

    Description:

    This function will have two jobs, get the printer status and act on it. It should first check and see if the printer is printing.  We only want to deactivate the temperature under the condition that the machine is not in the “printing” state. If the machine is in the printing state, then we want to reset the timer (call countdown). 

    This function has one new job. It will cancel the self.timer that called it and it will reset the boolean value that tells whether or not a timer instance is running back to false.

  6. On Settings Save

     settingssave

    Type:

    This is another back-end built in function from OctoPrint (like on after startup) that gets called every time someone hits the save button in settings. We are overloading the function to do its original job, as well as what we want it to do.

    Requirements:

    • User Specified timer must have already started for us to create a new timer
    • Must grab the old time
    • Must update the data
    • Must grab the new time
    • Compare old time to new time
      • Do nothing if they are the same
      • If they are different cancel the old timer and toggle the class variable saying one exists

    Description:

    We have overloaded this function to do our dirty work for us. The first thing that happens when someone presses the save button is that we grab the old time. The next thing that we do is update whatever data that they have saved. Then we grab the new time and compare it to the old time. If they have not changed, we do not do anything. If they have, and there is an existing timer already, we cancel the timer and allow our plugin to go through and create another timer instance.

The Implementation


We implemented Design 3.0. This was the final design that we as a group sat down and brainstormed once we realized that we had to replace our while loops with repeated timers. This design makes the most sense for our application. It eliminated the toggle check box that we had originally planned to let the user turn the plugin on or off. We realized that if they implement this plugin then they are probably going to want it on. We also realized that if they did not want it on, instead of checking a box, they could just set the default time to 0. This implementation does not start the user-specified Repeated Timer until both a time and temperature are  set to above 0. This means that the plugin is waiting to start the timer until it is needed.

Server Starts up -> Repeated Timer checks conditions function (every second) -> Once the conditions pass -> Call makeTimer which initiates our first user-specified Timer object -> When the time is up, the timer calls Shutdown function

 

actualcapt

Since we have implemented a Repeated Timer, we have the opportunity to see if the user has updated the time after we have started our timer. The timer is basically sleeping in the background until it is needed. This means if someone presses save in the OctoPrint interface, our plugin will still be able to detect it. This allowed us to overload the on settings save function and check to see if the time has been changed. If the time has been changed, and there has already been a timer created, we are able to cancel the existing timer, and let our plugin go back to checking the conditions function (where it will create a new countdown timer with the new time).

 

Challenges Encountered


There were several challenges throughout this process. The majority of them can be seen within the development of our design. While I will not highlight all of the troubles, I will point out the main frustrations that we encountered:

  1. Cookiecutter and the “Hello World!” plugin tutorial: While attempting to complete the plugin tutorial we continued to run into a range of issues. These would range from errors stating that we did not have the correct tools, to our plugin not showing up. One of my teammates, Dawn Manning, reached out to the community about the issue, and got a response that we could try and update cookiecutter. When she did update cookiecutter, she was able to get her plugin to show up. When Denzell (my original partner) and I did this, it stated that cookiecutter was up to date. We finally solved this by completely obliterating our build and starting from scratch, updating cookiecutter before we began creating the plugin. The complete instructions for overcoming this challenge can be found in this blog.
  2. Calling the Timer: This was a big design challenge for us. We did not know where exactly we needed to initiate the timer. If we initiated the timer on after startup, it would immediately start counting down which was not efficient or helpful if the bed temperature was not set. If we started the counter after they changed the time in settings (on settings save), it would start the timer, but lets assume they don’t mess with the default time, then our timer never starts. We decided that the best place to start the timer was whenever both the temperature and the time are set above 0. Our conditions function found in Design 3.0 and the implementation is what allows us to do this. It worked out very well to use a repeated timer so that as soon as both were set, it could initiate the user-specified timer.
  1. Eliminating While loops: This challenge was not as difficult as it was soul crushing. We had been so excited because we were just able to get our plugin working when we learned the sad news, I documented it in this sad blog. We were able to go through and eliminate all of our while loops by transforming them into repeated timers (if you are curious as to what they are/how they work read the sad blog above). These repeated timers actually worked perfectly for our plugin and gave us significant design advantages, like being able to set conditions for when our timer should initiate.
  2. What if the Time changes when the timer is already set? This challenge bugged us for a while, because we could not cancel the first instance of an initiated repeated timer and reset the time. However, we realized that we were creating only one instance of the timer, cancelling it and trying to re-start the same instance. This led us to instead cancel the instance and create a new instance, which works perfectly. You can see in Design 3.0 in the on settings save function that we grab the old time, and compare it to the new. If they are different, then we cancel the existing user timer, and toggle the class variable saying that there is not one to True. This allows our original timer to pass the conditions function and create a new instance of the user-initiated timer with the new time.

Next Steps


This plugin is virtually ready to be submitted to OctoPrint as a fix to #960 on the issue tracker. The only thing standing between our plugin and submission is testing. We have not tested our plugin while it is printing, and we do not have a hotbed to test and see if it will actually kill the temperature to the hotbed.

Points of Success


  1. Reaching out to the Community: This has been a huge success for our team. The community has helped us through challenges and gave us pointers on where to get started. Every time we reached out, someone gave us the most helpful answer. This is an example when we were trying to figure out how to make our plugin respond to temperature change. Here is their response when Dawn reached out about cookiecutter.
  2. Using Git: Github has intimidated me from day 1. It seemed like a foreign language that I would never get the hang of. However, I feel miles more comfortable about it after this class. For the past two months, we have been (somewhat) successfully using git to contribute to our plugin. It was definitely difficult at times since we were almost always working within the same file, and sometimes in the same function. This caused a lot of merge issues, however, in the end we got it all figured out. As long as you pull first thing, and then commit your changes and push them, you will 90 % of the time be just fine. The main lesson with git is communication, you need to talk to your team and keep them up to date with what you are working on to avoid merge issues (because boy are they ugly).
  3. Implementing a Plugin: Overall, I feel like this class has been a huge success for me. There were times when I could not see us finishing the plugin, but now that it is finished, it is a huge accomplishment. I had no idea how to get everything to interact with each other in a software setting like OctoPrint before this class. The Hello World! tutorial was extremely helpful, but the most rewarding thing was working with my group on this project. It was difficult at times when we all had different design ideas, and misunderstandings. However, in the end when everything has pulled together, it is very rewarding to look back and see all of your work. This could not have been possible without my team members!

octoteam

Teammates: Dawn Manning, Kristian Toole ,  Christopher Hunt

You can access the code from my github account.

Advertisements