Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,9 @@ debug_*.py
dist/
build/
*.egg-info/

# Auto-added by Marisol pipeline
.pio/
.gradle/
*.class
local.properties
682 changes: 682 additions & 0 deletions CLAUDE.md

Large diffs are not rendered by default.

Empty file modified PiDSLR.fzz
100644 → 100755
Empty file.
209 changes: 203 additions & 6 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,218 @@ git clone https://github.com/NickEngmann/pidslm.git
cd pidslm
```

You're then going to retrieve a Dropbox Access token to enable to Dropbox footage upload feature. To do this go ahead and [go to the Application Developer page on Dropbox](https://www.dropbox.com/developers/apps). Create an application and click the Generate Access Token button to generate your access token.
You're then going to retrieve a Dropbox Access token to enable the Dropbox footage upload feature. To do this go ahead and [go to the Application Developer page on Dropbox](https://www.dropbox.com/developers/apps). Create an application and click the Generate Access Token button to generate your access token.

Then replace the dummy access token in Dropbox_upload.py with your new access token.
Then replace the dummy access token in dropbox_upload.py with your new access token:

```python
# OAuth2 access token. TODO: login etc.
TOKEN = 'YOUR_ACCESS_TOKEN' # dropbox_upload.py line 19
```

# OAuth2 access token. TODO: login etc.
TOKEN = 'YOUR_ACCESS_TOKEN'
Finally, run the INSTALL.sh script using the following command:

```
sudo ./INSTALL.sh
```

Finally, run the INSTALL.sh script using the following command
# Features

## Capture Modes

The piDSLM provides multiple capture modes for different photography needs:

- **Manual Capture** - Press button or use Focus button for single shot (3.5s timeout)
- **Burst Mode** - Continuous capture for 10 seconds with 5-second intervals between shots
- **Timelapse** - Capture photos every 60 seconds for up to 1 hour
- **HD Video** - Record 30-second HD video clips
- **Split HD Video** - Record up to 30 minutes with 5-second segments automatically split
- **Long Preview** - 15-second preview mode for framing shots

## Gallery View

- Browse captured images in a full gallery window
- Navigate through images using left/right arrows
- Auto-detects all JPG files in the Downloads folder

## Dropbox Integration

- Upload photos and videos to Dropbox automatically
- Skip temporary files (dotfiles, py files, temp files)
- Compare file modification times to avoid redundant uploads
- Interactive prompts for file upload decisions
- Batch upload via command-line flags

# Hardware Requirements

- Raspberry Pi 2/3/4
- Raspberry Pi HQ Camera
- MHS35-TFT Display (3.5-inch TFT LCD)
- GPIO button connected to pin 16 (BCM numbering)
- Power supply for battery operation
- 3D printed enclosure (PiDSLR.fzz design file)

# Software Dependencies

See `requirements.txt` for the complete list of Python dependencies:

- Pillow - Image processing and manipulation
- guizero - Graphical user interface framework
- dropbox - Dropbox API SDK for file uploads
- guizero[images] - Image support for guizero widgets

# File Structure

```
sudo ./INSTALL.sh
piDSLM/
├── pidslm.py # Main GUI application (167 lines)
├── dropbox_upload.py # Dropbox sync utility (273 lines)
├── INSTALL.sh # Installation script (17 lines)
├── pidslm.desktop # Desktop auto-start configuration (5 lines)
├── PiDSLR.fzz # 3D enclosure design (Fusion 360)
├── requirements.txt # Python dependencies (4 lines)
├── README.md # This documentation file
├── MARISOL.md # Pipeline context documentation
├── icon/ # GUI icon assets
│ ├── cam.png # Capture button icon
│ ├── gallery.png # Gallery button icon
│ ├── drop.png # Dropbox upload icon
│ ├── del.png # Clear folder icon
│ ├── prev.png # Preview button icon
│ ├── vid.png # Video capture icon
│ ├── lapse.png # Timelapse icon
│ ├── long.png # Long preview icon
│ ├── self.png # Self-timer icon
│ ├── 100black.png # 100% zoom icon
│ └── 100trans.png # 100% transparent icon
└── tests/ # Test suite
├── conftest.py # Hardware mocks and fixtures (176 lines)
├── embedded_mocks.py # Mock hardware classes
└── test_example.py # Example test cases
```

# Usage Guide

## Starting the Application

After installation, the application will automatically start when the Raspberry Pi boots. The main window displays a grid of buttons:

| Button | Function |
|--------|----------|
| Focus | 15-second live preview mode |
| Gallery | View all captured images |
| HD 30s | Record 30-second HD video |
| Burst | Continuous capture for 10 seconds |
| 1h 60pix | Timelapse: 1 hour at 60-second intervals |
| HD 30m in 5s | Split video: 30 minutes in 5-second segments |
| Upload | Bulk upload to Dropbox |
| Clear Folder | Delete all files in Downloads folder |

## GPIO Button Trigger

The physical button connected to GPIO pin 16 (BCM) automatically triggers photo capture when pressed. The button has a 2.5-second bounce time to prevent accidental double triggers.

## File Storage Locations

- **Images**: `/home/pi/Downloads/*.jpg`
- **Videos**: `/home/pi/Downloads/*.h264`
- **Burst mode**: `/home/pi/Downloads/BRYYYYMMDD_HHMMSS%04d.jpg`
- **Timelapse**: `/home/pi/Downloads/TLYYYYMMDD_HHMMSS%04d.jpg`
- **Split video**: `/home/pi/Downloads/YYYYMMDD_HHMMSSvid%04d.h264`

# Command-Line Options (dropbox_upload.py)

The Dropbox upload script supports the following options:

```
usage: dropbox_upload.py [-h] [--token TOKEN] [--yes] [--no] [--default]
[folder] [rootdir]

positional arguments:
folder Folder name in your Dropbox (default: Downloads)
rootdir Local directory to upload (default: ~/Downloads)

optional arguments:
-h, --help Show this help message and exit
--token TOKEN
Access token for Dropbox API
--yes, -y Answer yes to all questions
--no, -n Answer no to all questions
--default, -d
Take default answer on all questions
```

# Testing

Run the test suite with pytest:

```bash
cd tests/
python3 -m pytest -v
```

Current test coverage:
- GPIO pin control (test_gpio_pin_control)
- I2C communication simulation (test_i2c_communication)

The test framework uses mock hardware modules to enable testing without physical Raspberry Pi hardware.

# Troubleshooting

## GPIO Button Not Working

- Check that the button is connected to GPIO pin 16 (BCM)
- Verify wiring is correct (button between GPIO pin and ground)
- Check that RPi.GPIO module is properly installed

## Dropbox Upload Fails

- Ensure TOKEN in dropbox_upload.py is set to a valid access token
- Verify internet connectivity on the Raspberry Pi
- Check Dropbox API permissions for the configured app

## Gallery Won't Load

- Ensure JPG files exist in /home/pi/Downloads/
- Check file permissions for the Downloads directory
- Verify guizero image loading is working

# Development

## Running Without Installation

To test the application without full installation:

```bash
python3 pidslm.py
```

Note: This requires the icon directory and dependencies to be available.

## Modifying the Application

Key files to modify:
- `pidslm.py` - Main application logic and GUI
- `dropbox_upload.py` - Dropbox synchronization logic
- `INSTALL.sh` - Installation and setup process

# License

This project is based on the MerlinPi project. See the original repository for licensing information.

# Contributing

The design includes modular camera grips for users. Feel free to create your own 3D designs and reach out to the author for inclusion in the project.

# Credits

- **Original Concept**: Nick Engmann
- **Base Code**: Martin Manders (MerlinPi project)
- **Design Software**: OnShape
- **Camera**: Raspberry Pi HQ Camera
- **Display**: MHS35-TFT LCD

---

*Last updated: 2026-03-30*
*Documentation version: 1.2*
51 changes: 50 additions & 1 deletion dropbox_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,51 @@
# OAuth2 access token. TODO: login etc.
TOKEN = 'YOUR_ACCESS_TOKEN'

<<<<<<< Updated upstream
=======

def should_skip_file(filename):
"""Check if a file should be skipped based on its name.

Args:
filename: The name of the file to check.

Returns:
True if the file should be skipped, False otherwise.
"""
if not isinstance(filename, six.text_type):
try:
filename = filename.decode('utf-8')
except (UnicodeDecodeError, AttributeError):
filename = str(filename)

if filename.startswith('.'):
return True
if filename.startswith('@') or filename.startswith('~') or filename.endswith('~'):
return True
if filename.endswith('.pyc') or filename.endswith('.pyo'):
return True
return False


def should_skip_directory(dirname):
"""Check if a directory should be skipped based on its name.

Args:
dirname: The name of the directory to check.

Returns:
True if the directory should be skipped, False otherwise.
"""
if dirname.startswith('.'):
return True
if dirname.startswith('@') or dirname.startswith('~') or dirname.endswith('~'):
return True
if dirname == '__pycache__':
return True
return False

>>>>>>> Stashed changes
parser = argparse.ArgumentParser(description='Sync ~/Downloads to Dropbox')
parser.add_argument('folder', nargs='?', default='Downloads',
help='Folder name in your Dropbox')
Expand Down Expand Up @@ -180,7 +225,11 @@ def upload(dbx, fullname, folder, subfolder, name, overwrite=False):
except dropbox.exceptions.ApiError as err:
print('*** API error', err)
return None
print('uploaded as', res.name.encode('utf8'))
# Handle both bytes and string response names (Python 3 compatibility)
name_output = res.name
if isinstance(name_output, bytes):
name_output = name_output.decode('utf-8')
print('uploaded as', name_output)
return res

def yesno(message, default, args):
Expand Down
Empty file modified icon/100black.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/100trans.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/cam.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/del.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/drop.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/gallery.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/lapse.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/left.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/long.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/prev.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/right.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/self.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified icon/vid.png
100644 → 100755
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file modified pidslm.desktop
100644 → 100755
Empty file.
Empty file modified requirements.txt
100644 → 100755
Empty file.
Empty file modified tests/conftest.py
100644 → 100755
Empty file.
Empty file modified tests/embedded_mocks.py
100644 → 100755
Empty file.
Loading