Need help with plugin creation for a timelapse

you're also missing the binding here:

should be something like elements: ["#settings_plugin_Sla_Timelapse"]

i have to say that you really have the paciance to put up with this kind of dumb questios and for that THANK YOU

also, capitalization may actually matter. your template file is sla_timelapse_settings.jinja2, but your identifier is Sla_Timelapse.

edit: or define the template filename in this call:

as

dict(type="settings", custom_bindings=False, template="sla_timelapse_settings.jinja2")

crap my bad.... i had that in an old test.... but still doesn't show... that's way i removed it

anyway just checked the logs and when i activate the GPIO is does trigger a log output

2024-02-10 23:10:31,978 - octoprint.plugins.Sla_Timelapse - INFO - LDR Activated
2024-02-10 23:10:32,501 - octoprint.plugins.Sla_Timelapse - INFO - LDR Deactivated... waiting for photo in 5 seconds
2024-02-10 23:10:51,982 - octoprint.util.comm - INFO - Changing monitoring state from "Offline" to "Opening serial connection"
2024-02-10 23:10:52,004 - octoprint.util.comm - INFO - Connecting to port /dev/ttyS0, baudrate 115200
2024-02-10 23:10:52,022 - octoprint.util.comm - INFO - Changing monitoring state from "Opening serial connection" to "Connecting"
2024-02-10 23:10:52,052 - octoprint.plugins.chituboard - INFO - Replacing ok V4.3.13_LCDC
with ok start

however the 3rd step never happens

   def take_snapshot(self):
        # Check if the timer has elapsed configured seconds
        snapshot_delay = self._settings.get_int(["snapshot_delay"])
        if self.snapshot_timer is not None and time.time() - self.snapshot_timer >= snapshot_delay:
            self._printer.commands(["@OCTOLAPSE TAKE-SNAPSHOT"])
            self.snapshot_timer = None
            self._logger.info("Snapshot taken!")

"Snapshot taken!"

an i do have octolapse installed

there's nowhere in your code that you're calling that function, so it's not surprising it's not happening.

so here:

add the line

self.take_snapshot()

although, if you're trying to do what I think you're trying to do and wait x amount of seconds prior to sending the command to take the snapshot, it might be a better use of thread.timer, like this.

    def handle_ldr_state(self, channel):
        if GPIO.input(channel):  # LDR Deactivated
            self._logger.info("LDR Deactivated... waiting for photo in {} seconds".format(self._settings.get_int(["snapshot_delay"])))
            t = threading.Timer(self._settings.get_int(["snapshot_delay"]), self._printer.commands, ["@OCTOLAPSE TAKE-SNAPSHOT"])
            t.start()

once i don't know how to code... it's really a gessing game..... iif and a big IF e get this to work, can e credit you on all the help ?

thank you

h have made the changes and now i get in the log output

2024-02-11 12:06:21,817 - octoprint.plugins.Sla_Timelapse - INFO - LDR Activated
2024-02-11 12:06:23,824 - octoprint.plugins.Sla_Timelapse - INFO - LDR Deactivated... waiting for photo in 5 seconds
2024-02-11 12:06:23,825 - octoprint.cli.server - ERROR - Uncaught exception
Traceback (most recent call last):
  File "/home/pi/oprint/lib/python3.9/site-packages/octoprint_Sla_Timelapse/__init__.py", line 26, in handle_ldr_state
    t = threading.Timer(self._settings.get_int(["snapshot_delay"]), self._printer.commands, ["@OCTOLAPSE TAKE-SNAPSHOT"])
NameError: name 'threading' is not defined
2024-02-11 12:07:06,793 - octoprint.plugins.Sla_Timelapse - INFO - LDR Activated

for what i can see i need to define "threading" but i don't know what is it about or how to define it

Threading is a Python built in module. You can import it with import threading.

thank you for reply but no errors and no snapshot

I was curious if sending the command from a plugin would process in other plugins. Do you see the @OCTOLAPSE TAKE-SNAPSHOT command in the terminal tab during the process or not?

No ... nothing

any idea on how to trigger the snapshot ?

in my stand-alone python code, this does work, could we adapt it ?

def button_released():
    global trigger_count
    if trigger_count > triggers_to_ignore:
        print_with_timestamp("LDR Off... waiting to take photo!")
        time.sleep(args.time)  # Sleep for the specified time

        # Create a timestamp for the filename (including date and time)
        photo_timestamp = datetime.now().strftime("%d-%m-%Y_%H:%M:%S")
        photo_path = f"{new_folder}/timelapse_{photo_timestamp}.jpg"

        try:
            # Capture a photo using wget
            subprocess.run(["wget", f"http://localhost:8080/?action=snapshot", "-O", photo_path])

            print_with_timestamp(f"Photo saved at {photo_path}")

        except Exception as e:
            print_with_timestamp(f"Error capturing photo: {e}")

i really don't know how to do it

ty

try the changes I submitted in this pull request.

thank you mate for all the help

i have made some progress on my own but i am still stuck
now i am able to take snapshots using the localhost method and with hardcoded settings.
the next part is to be able to set them using the settings template page an i am getting nowhere and i could use some help... again,,,,

here is my progress

working code with hardcoded settings

"""
OctoPrint plugin to automatically take timelapse snapshots  
using a light dependent resistor (LDR) sensor. v2 (22/02/2024)
"""

import threading
import datetime
import logging
import os

import requests 
import RPi.GPIO as GPIO
from octoprint.plugin import StartupPlugin, TemplatePlugin, SettingsPlugin, AssetPlugin


# Module constants
LDR_PIN = 21  
PHOTO_DELAY = 5 # seconds

# Setup logging
log = logging.getLogger("octoprint.plugins." + __name__)


class SlaTimelapsePlugin(StartupPlugin, TemplatePlugin, 
                          SettingsPlugin, AssetPlugin):
    """
    OctoPrint plugin to take timelapse snapshots using a LDR sensor.
    """
    
    def __init__(self):
        self.photo_in_progress = False

        # Initialize settings dictionary
        self.settings = dict(
            ldr_pin=LDR_PIN,
            photo_delay=PHOTO_DELAY,
            snapshot_folder="/home/pi/timelapse"
        )
    
    
    def on_after_startup(self):
        self._setup_gpio()


    def get_settings_defaults(self):
        return self.settings
        

    def _setup_gpio(self):
        """Initialize GPIO sensor pin."""
        GPIO.setmode(GPIO.BCM) 
        GPIO.setup(self.settings['ldr_pin'], GPIO.IN, GPIO.PUD_UP)
        GPIO.add_event_detect(self.settings['ldr_pin'], 
            GPIO.BOTH, callback=self._ldr_changed, bouncetime=300)


    def _ldr_changed(self, channel):
        """Take snapshot when LDR state changes."""
        if GPIO.input(channel) and not self.photo_in_progress:
            log.info("LDR deactivated - Waiting to take photo")   
            self.photo_in_progress = True
            threading.Timer(self.settings['photo_delay'], self._take_snapshot).start()

        elif not GPIO.input(channel) and self.photo_in_progress:
            log.info("LDR activated - Canceling snapshot")
            self.photo_in_progress = False 


    def _take_snapshot(self):
        """Capture and save snapshot image."""
        try:
            response = requests.get("http://localhost:8080/webcam/?action=snapshot")
            if response.status_code == 200:
                timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
                filename = f"snapshot_{timestamp}.jpg"
                
                # Use context manager to save snapshot 
                with open(os.path.join(self.settings['snapshot_folder'], filename), "wb") as f:
                    f.write(response.content)
                
                log.info(f"Saved snapshot to {filename}")
            else:
                log.warning(f"Failed taking snapshot - status {response.status_code}")

        except Exception as e:
            log.exception(f"Failed to save snapshot: {e}")
        
        finally:
            self.photo_in_progress = False

__plugin_name__ = "Sla Timelapse"
__plugin_pythoncompat__ = ">=3.7,<4"  

def __plugin_load__():
    global __plugin_implementation__
    __plugin_implementation__ = SlaTimelapsePlugin()

here is a sample of the slatimelapse_settingns.jinja2


    <h1>SLA Timelapse Settings</h1>

    <form action="#" method="post">
        <label for="gpio_pin">GPIO Pin:</label>
        <select id="gpio_pin" name="gpio_pin">
            {% for pin in available_pins %}
                <option value="{{ pin }}" {% if plugin_settings.gpio_pin == pin %}selected{% endif %}>{{ pin }}</option>
            {% endfor %}
        </select>

        <button type="submit">Save</button>
    </form>

note: i know that in the template i have code to call/read the pins and if is not done in the init.py and that is my problem, how to setup a pin in the template and make the plugin use it

thank you again

The file you have uploaded to GitHub should work properly. Notice the bit data-bind="value: settings.plugins.Sla_Timelapse.gpio_pin" is what glues the settings and the UI using knockout.

thank you for the reply.
i did try with that and it loads the template howeve 2 questions
how to i handle the submition to save as i am getting

Method Not Allowed
The method is not allowed for the requested URL.

and, i have to remove the hardcoded settings don't i ? what was prioraty ? the hardcoded os the template ?

# Module constants
LDR_PIN = 21  
PHOTO_DELAY = 5 # seconds

regards

one other question, is there a way to modify the default octoprint buildin timelapse plugin but to use a pi gpio to trigger it ?

after a small reading across google and by looking at octorelay, a came to realise the octorelay saves the relay settings in config.yalm and i think i have to do the same.

the problem is... how to do it

Ah I see the missing piece. You don't have octoprint.plugin.TemplatePlugin in your class definition.

I just updated my fork of your plugin with the necessary tweaks but looks like you've since deleted all the files and it's showing conflict.

I noticed you didn't have RPi.GPIO in the serup requires, so added that too. You can see my changes at the link below. I installed on a pi zero and the default port 21 shows in settings as expected.

@ jneilliii

BIGGG UPSSS

just realised that GITHUB did not update automalicly....
i have just uploaded the latest version (New version)
Changes:

  • new and working init.py
  • Updated setup.py
  • new template slatimelapse_settings.jinja2

A verry big i'm sorry about that

Issues:

  • Ability to use settings.jinja2 to configure the plugin
  • where to save the data so init.py can use it
  • how to read back the stored data in settings.jinja2