Can't get settings values to show up in dialog

I'm a noob to writing octoprint plugins (but have programming experience) and working on my first plugin, which is a pretty simple one that monitors the printer temperature and heats it up if it gets below a certain value to prevent a MINTEMP error.
However, I can't for the life of me get my settings to show up on the plugin settings page. It does create a settings page for my plugin and displays a label and text entry box, but it does not pull the settings value into it (i.e. text is always blank). Also, the "Saving" button has a permanent spinner on it and cannot be clicked. My understanding is that this means there's something wrong with the way I have my settings setup.

BTW. My .jinja2 file doesn't get picked up automatically. I have to include an explicit template="PreventMINTEMP_settings.jinja2" in my get_template_configs. Note sure if this means anything or if there's some case-sensitivity going on here.

Would really appreciate your help.

Here's my init.py file:

import octoprint.plugin
import octoprint.util
import octoprint.printer

heating = False

class PreventmintempPlugin(octoprint.plugin.StartupPlugin,
    octoprint.plugin.SettingsPlugin,
    octoprint.plugin.TemplatePlugin
):

    def check_temp(self):
        #global count
        heat_thresh = 10.0 #settings.preventMINTEMP.heat_thresh
        ignore_thresh = -10.0 #settings.preventMINTEMP.ignore_thresh
        heat_target = 30.0 #settings.preventMINTEMP.heat_target
        global heating
         
        #count += 1
        print("Checking temperature")
        pstate = self._printer.get_state_id()
        if(pstate != "PRINTING"):  #Don't do monitoring if we're printing
            tdict = self._printer.get_current_temperatures()
            all_at_target = True
            for x in tdict:
                #print(x, ": ", tdict[x])
                if(x[:4] == "tool" or x == "bed"):
                    elt_name = x
                    T = tdict[x]["actual"]
                    Ttarg = tdict[x]["target"]
                    if(Ttarg == 0 and T > ignore_thresh and T < heat_thresh):
                            print(f"Element {x} is below {heat_thresh}C temperature threshold. Heating element",x)
                            all_at_target = False
                            heating = True
                            self._printer.set_temperature(elt_name,heat_target)
                    else:
                        # If we're heating this element and we reached target, then turn it off
                        if(Ttarg != 0.0 and (T >= heat_target or T < ignore_thresh)):
                            if(T > ignore_thresh):
                                print(f"Element {elt_name} reached {heat_target}C target. Turning off heater.")
                            else:
                                print(f"Element {elt_name} is below error temperature.")
                            self._printer.set_temperature(elt_name, 0.0)
                        elif(Ttarg != 0.0):
                            all_at_target = False
            if(all_at_target and heating):
                print("All elements above temperature target.")
                heating = False
        else:
            print("Printer is printing")
    
    def on_after_startup(self):
        check_period = 5.0 #settings.plugins.PreventMINTEMP.check_period
        self._logger.info("Prevent MINTEMP started!")
        print("Prevent MINTEMP started...")
        t = octoprint.util.RepeatedTimer(check_period,self.check_temp)
        t.start()

    ##~~ SettingsPlugin mixin

    def get_settings_defaults(self):
        return {
            'heat_thresh': 15.0,		#lower temperature threshold to start heating
            'ignore_thresh': -10.0,	#temperature below which we are in MINTEMP error, so will not heat
            'heat_target': 30.0,		#target temperture to which to heat elements
            'check_period': 5.0		#how often to check temperature in seconds
        }

    def get_template_configs(self):
        return [
            dict(type="settings", template="PreventMINTEMP_settings.jinja2", custom_bindings=False)
        ]

# If you want your plugin to be registered within OctoPrint under a different name than what you defined in setup.py
# ("OctoPrint-PluginSkeleton"), you may define that here. Same goes for the other metadata derived from setup.py that
# can be overwritten via __plugin_xyz__ control properties. See the documentation for that.
__plugin_name__ = "Prevent MINTEMP"


# Set the Python version your plugin is compatible with below. Recommended is Python 3 only for all new plugins.
# OctoPrint 1.4.0 - 1.7.x run under both Python 3 and the end-of-life Python 2.
# OctoPrint 1.8.0 onwards only supports Python 3.
__plugin_pythoncompat__ = ">=3,<4"  # Only Python 3

def __plugin_load__():
    global __plugin_implementation__
    __plugin_implementation__ = PreventmintempPlugin()
#
#    global __plugin_hooks__
#    __plugin_hooks__ = {
#        "octoprint.plugin.softwareupdate.check_config": __plugin_implementation__.get_update_information
#    }

and here's my preventMINTEMP

<form class="form-horizontal">
    <div class="control-group">
        <label class="control-label">{{ _('Heating Threshold') }}</label>
        <div class="controls">
            <input type="text" class="input-block-level" data-bind="value: settings.plugins.PreventMinTEMP.heat_thresh">
        </div>
    </div>
</form>

What's your plugin_idnentifier you used? If it's not exactly PreventMinTEMP then this won't bind properly.

Working example: OctoPrint-ipOnConnect/octoprint_ipOnConnect/templates/ipOnConnect_settings.jinja2 at 30b8d9eeb8d1e801f997e6378c6f550194f32650 · jneilliii/OctoPrint-ipOnConnect · GitHub with plugin_identifier plugin_identifier defined in setup.py: https://github.com/jneilliii/OctoPrint-ipOnConnect/blob/30b8d9eeb8d1e801f997e6378c6f550194f32650/setup.py#L7

Probably easiest way is to look in the logging section of OctoPrint, what is your detected plugin_identifier in the select list at the bottom?

image

Brilliant! It worked! Turned out that the name was "preventMINTEMP." I tried all kinds of capitalization combination, but not that one, and wasn't sure where to check. I assumed that the name would be the same as the plugin folder, which is "octoprint_PreventMINTEMP". Where does the exact name actually come from if it's not any of the directory names? How does octoprint determine it?
Thanks a lot for your help.

It's typically defined in the setup.py file or project.toml, or in your init.py file the way that you have plugin name defined toward the end. If it's not defined in any I think it's basically what you saw where the first letter is lower cased and the rest remains the same. I'm sure there's some pep number in python that defines this.

To be honest, I've found that using uppercase letters at all for these makes things a little more difficult. Over the years I've started using all lowercase characters for those plugin identifiers.

This is also why it doesn't auto load the file, but you'd have to define get_template_configs anyway because of the custom_binding=False. Without that the auto loaded file would custom bind and you'd have to define your elements to bind to and modify the value binding in the js/jinja files.