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
Empty file modified PiDSLR.fzz
100644 → 100755
Empty file.
201 changes: 201 additions & 0 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,204 @@ sudo ./INSTALL.sh



piDSLM - Raspberry Pi Digital Single Lens Mirrorless
===============

Camera project for Raspberry Pi 2/3 + HQ Camera + MHS35-TFT

<img src="https://i.imgur.com/VspFA5V.jpg" data-canonical-src="https://i.imgur.com/VspFA5V.jpg" width="400" height="400" />

# Introduction

Made an enclosure to host the [HQ Raspberry Pi Camera](https://www.raspberrypi.org/products/raspberry-pi-high-quality-camera/) as a standalone battery-powered DSLM that I'm calling piDSLM. Check out the links below for instructions on how to recreate the project!

The design includes a few modular camera grips for users. Feel free to make your own designs and reach out to me so I can include them!

For More Info:

- [Hackster](https://www.hackster.io/projects/2a86c3)
- [GitHub](https://github.com/NickEngmann/piDSLM)
- [Instructables] ( TBD )

Designed using
- [OnShape](https://bit.ly/raspi-onshape)

If you found this useful, please donate what you think it is worth to my [paypal.me](https://paypal.me/nickengman). Help cover the time of design.

Thanks, Enjoy!

# Installation

For the codebase, I built the piDSLM codebase off of a forked a copy of fellow DIYer Martin Manders [MerlinPi project](https://github.com/MisterEmm/MerlinPi). The piDSLM codebase is still in its infancy but it allows the user to take photos/videos, and view them in a gallery. It also allows users to bulk upload the footage to Dropbox. To begin ssh into the Raspberry Pi and run the following command:

```

git clone https://github.com/NickEngmann/pidslm.git
cd pidslm
```

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.

```

# OAuth2 access token. TODO: login etc.
TOKEN = 'YOUR_ACCESS_TOKEN'
```

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

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

# Features

## Camera Controls

The piDSLM application provides a comprehensive GUI with multiple camera functions:

- **Focus Preview**: 15-second live preview for focusing (line 74 in pidslm.py)
- **Gallery View**: Browse captured photos and videos (line 91 in pidslm.py)
- **Video Capture**: Record 30-second HD video clips (line 98 in pidslm.py)
- **Burst Mode**: Capture up to 10,000 images in rapid succession (line 59 in pidslm.py)
- **Timelapse**: Capture 60 images over 1 hour with 60-second intervals (line 66 in pidslm.py)
- **Split HD**: Record 30-minute videos in 5-second segments (line 71 in pidslm.py)
- **Upload to Dropbox**: Bulk upload all footage from Downloads folder (line 107 in pidslm.py)
- **Clear Folder**: Remove all files from Downloads directory (line 52 in pidslm.py)

## GPIO Button Control

A physical button connected to GPIO pin 16 triggers photo capture automatically (line 15-16 in pidslm.py):

- Uses BCM GPIO numbering scheme
- FALLING edge detection with 2500ms bouncetime
- Automatically timestamps and saves photos to /home/pi/Downloads/

## Hardware Requirements

- **Raspberry Pi**: Model 2 or Model 3 recommended
- **Camera**: Raspberry Pi High Quality (HQ) Camera
- **Display**: 3.5" TFT display (MHS35-TFT)
- **GPIO**: 40-pin header for button connection
- **Storage**: microSD card with sufficient space for photos/videos

# Technical Details

## Project Structure

```
piDSLM/
├── pidslm.py # Main application (134 lines)
├── dropbox_upload.py # Dropbox sync utility (158 lines)
├── INSTALL.sh # Installation script (16 lines)
├── pidslm.desktop # Auto-start configuration
├── PiDSLR.fzz # 3D enclosure design (Fusion 360)
├── icon/ # UI icons (14 PNG files)
└── tests/ # Test suite
├── test_example.py # Hardware test examples
├── conftest.py # Test configuration
└── embedded_mocks.py # Hardware mocks
```

## Dependencies (from requirements.txt)

| Package | Purpose |
|---------|---------|
| Pillow | Image processing and display |
| guizero | GUI framework for camera controls |
| dropbox | Dropbox API integration for uploads |
| guizero[images] | Additional image support |

## Key File Locations

- **Application**: `/home/pi/piDSLM/pidslm.py` (line 7)
- **Upload Script**: `/home/pi/piDSLM/dropbox_upload.py` (line 19)
- **Captured Media**: `/home/pi/Downloads/`
- **Icons Directory**: `/home/pi/piDSLM/icon/`

# Testing

## Run Tests

Execute the test suite using pytest:

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

## Current Test Coverage

- **test_gpio_pin_control**: Tests GPIO pin output (HIGH/LOW state control)
- **test_i2c_communication**: Tests I2C bus read/write operations

## Hardware Mocking

The test suite uses `embedded_mocks.py` to simulate Raspberry Pi hardware:

- **MockGPIO**: Simulates RPi.GPIO pin control
- **MockI2C**: Simulates I2C bus communication
- **MockSPI**: Simulates SPI bus communication
- **MockUART**: Simulates UART serial communication

## Test Configuration

`conftest.py` provides:

- Auto-mocking of 15+ Raspberry Pi hardware modules
- `source_module` fixture for loading project code with while-True loops stripped
- Realistic GPIO constants (BCM=11, HIGH=1, LOW=0, etc.)

# Troubleshooting

## Dropbox Upload Issues

1. **Error**: "TOKEN is mandatory"
- **Solution**: Ensure TOKEN variable is set in dropbox_upload.py (line 19)

2. **Error**: "Folder listing failed"
- **Solution**: Check Dropbox API permissions and token validity

3. **Error**: "does not exist on your filesystem"
- **Solution**: Verify /home/pi/Downloads/ directory exists

## GPIO Button Not Working

1. **Check Wiring**: Ensure button connects GPIO pin 16 to GND
2. **Check Permissions**: Run with sudo or add user to gpio group
3. **Verify Mode**: Check `GPIO.setmode(GPIO.BCM)` is used (line 13)

## Gallery Display Issues

1. **Error**: "saved_pictures list empty"
- **Solution**: Ensure .jpg files exist in /home/pi/Downloads/

2. **Error**: "Picture not found"
- **Solution**: Verify file path matches glob pattern `/home/pi/Downloads/*.jpg`

## Hardware Not Detected

1. **Camera Not Found**: Run `vcgencmd get_camera` to verify detection
2. **GPIO Issues**: Check `ls /sys/class/gpio/` for exported pins
3. **Display Issues**: Verify /boot/config.txt has `start_x=1` and `gpu_mem=128` (INSTALL.sh line 14)

# Contributing

This project is a fork of [MerlinPi](https://github.com/MisterEmm/MerlinPi) by Martin Manders. Features include:

- Enhanced enclosure design (PiDSLR.fzz)
- Dropbox integration for automated uploads
- Custom GPIO button triggers
- Timelapse and burst mode improvements

# License

Based on the MerlinPi project. This fork adds custom features and is distributed under the same terms.

# Credits

- **Original Project**: Martin Manders (MerlinPi)
- **Fork Author**: Nick Engmann (piDSLM)
- **Hardware**: Raspberry Pi Foundation, Pimoroni (MHS35-TFT)
- **Design**: OnShape (cloud-based CAD platform)
84 changes: 84 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Configuration module for piDSLM.

Provides centralized configuration management for:
- File paths (Downloads, icons, etc.)
- Dropbox settings
- GPIO settings
"""
import os
from dataclasses import dataclass
from typing import Optional


@dataclass
class PiDSLMConfig:
"""Configuration for the Pi DSLM camera interface."""

# Paths
downloads_dir: str = os.path.expanduser("~/Downloads")
icon_dir: str = os.path.join(os.path.dirname(__file__), "icon")
home_dir: str = os.path.expanduser("~/pi")

# Dropbox settings
dropbox_folder_name: str = "Downloads"
dropbox_enabled: bool = False
dropbox_token: Optional[str] = None

# GPIO settings
button_pin: int = 16
button_mode: str = "BCM"
button_bounce_time: int = 2500

# Capture settings
burst_duration_ms: int = 10000
lapse_duration_ms: int = 3600000
lapse_interval_ms: int = 60000
video_capture_duration_ms: int = 30000
split_video_total_duration_ms: int = 1800000
split_video_segment_ms: int = 300000

@classmethod
def from_env(cls) -> "PiDSLMConfig":
"""Create configuration from environment variables."""
config = cls()

downloads_path = os.environ.get("PIDSLM_DOWNLOADS_DIR")
if downloads_path:
config.downloads_dir = os.path.expanduser(downloads_path)

icon_path = os.environ.get("PIDSLM_ICON_DIR")
if icon_path:
config.icon_dir = icon_path

dropbox_token = os.environ.get("DROPBOX_ACCESS_TOKEN")
if dropbox_token:
config.dropbox_enabled = True
config.dropbox_token = dropbox_token

return config

def get_icon_path(self, icon_name: str) -> str:
"""Get full path to an icon file."""
return os.path.join(self.icon_dir, f"{icon_name}.png")

def get_capture_output_path(self, filename: str) -> str:
"""Get full path to capture output in downloads directory."""
return os.path.join(self.downloads_dir, filename)


# Global configuration instance (will be set by main app)
config: Optional[PiDSLMConfig] = None


def get_config() -> PiDSLMConfig:
"""Get the global configuration, creating default if needed."""
global config
if config is None:
config = PiDSLMConfig.from_env()
return config


def set_config(cfg: PiDSLMConfig):
"""Set the global configuration."""
global config
config = cfg
Loading