This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

1import RPi.GPIO as GPIO 2import time 3from picamera2 import Picamera2 4from datetime import datetime 5import os 6import logging 7import http.server 8import socketserver 9import threading 10 11# Setup logging 12logger = logging.getLogger('camera_server') 13logger.setLevel(logging.INFO) 14formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 15file_handler = logging.FileHandler('/home/kierank/camera_server.log') 16file_handler.setFormatter(formatter) 17stream_handler = logging.StreamHandler() 18stream_handler.setFormatter(formatter) 19logger.addHandler(file_handler) 20logger.addHandler(stream_handler) 21 22class Config: 23 BUTTON_PIN = 17 24 PHOTO_DIR = "/home/kierank/photos" 25 WEB_PORT = 80 26 PHOTO_RESOLUTION = (2592, 1944) 27 CAMERA_SETTLE_TIME = 1 28 DEBOUNCE_DELAY = 0.2 29 POLL_INTERVAL = 0.01 30 31def validate_photo_dir(): 32 if not os.path.isabs(Config.PHOTO_DIR): 33 raise ValueError("PHOTO_DIR must be an absolute path") 34 if not os.access(Config.PHOTO_DIR, os.W_OK): 35 raise PermissionError(f"No write access to {Config.PHOTO_DIR}") 36 37# Ensure photo directory exists and is valid 38validate_photo_dir() 39os.makedirs(Config.PHOTO_DIR, exist_ok=True) 40 41# Set up GPIO 42GPIO.setmode(GPIO.BCM) 43GPIO.setup(Config.BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) 44 45# Create a simple HTML gallery template - using triple quotes properly 46HTML_TEMPLATE = """<!DOCTYPE html> 47<html> 48<head> 49 <title>Inkpress: Gallery</title> 50 <meta name="viewport" content="width=device-width, initial-scale=1"> 51 <style> 52 body {{ font-family: Arial; max-width: 800px; margin: 0 auto; padding: 20px; }} 53 h1 {{ text-align: center; }} 54 .gallery {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }} 55 .photo {{ border: 1px solid #ddd; padding: 5px; }} 56 .photo img {{ width: 100%; height: auto; }} 57 .photo a {{ display: block; text-align: center; margin-top: 5px; }} 58 </style> 59</head> 60<body> 61 <h1>Inkpress: Gallery</h1> 62 <div class="gallery"> 63 {photo_items} 64 </div> 65</body> 66</html> 67""" 68 69class PhotoHandler(http.server.SimpleHTTPRequestHandler): 70 def __init__(self, *args, **kwargs): 71 super().__init__(*args, directory=Config.PHOTO_DIR, **kwargs) 72 73 def do_GET(self): 74 if self.path == '/': 75 self.send_response(200) 76 self.send_header('Content-type', 'text/html') 77 self.send_header('X-Content-Type-Options', 'nosniff') 78 self.send_header('X-Frame-Options', 'DENY') 79 self.send_header('X-XSS-Protection', '1; mode=block') 80 self.end_headers() 81 82 # Generate photo gallery HTML 83 photo_items = "" 84 try: 85 files = sorted(os.listdir(Config.PHOTO_DIR), reverse=True) 86 for filename in files: 87 if filename.lower().endswith(('.jpg', '.jpeg', '.png')): 88 timestamp = filename.replace('photo_', '').replace('.jpg', '') 89 photo_items += f""" 90 <div class="photo"> 91 <img src="/{filename}" alt="{timestamp}"> 92 <a href="/{filename}" download>Download</a> 93 </div> 94 """ 95 96 if not photo_items: 97 photo_items = "<p style='grid-column: 1/-1; text-align: center;'>No photos yet. Press the button to take a photo!</p>" 98 except Exception as e: 99 logger.error(f"Error generating gallery: {str(e)}") 100 photo_items = f"<p>Error loading photos: {str(e)}</p>" 101 102 html = HTML_TEMPLATE.format(photo_items=photo_items) 103 self.wfile.write(html.encode()) 104 else: 105 super().do_GET() 106 107def take_photo(): 108 """ 109 Captures a photo using the Raspberry Pi camera. 110 111 The photo is saved with a timestamp in the configured photo directory. 112 The camera is configured for still capture at the specified resolution. 113 114 Raises: 115 IOError: If there's an error accessing the camera or saving the file 116 """ 117 try: 118 with Picamera2() as picam2: 119 config = picam2.create_still_configuration(main={"size": Config.PHOTO_RESOLUTION}) 120 picam2.configure(config) 121 picam2.start() 122 time.sleep(Config.CAMERA_SETTLE_TIME) 123 124 timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 125 filename = f"{Config.PHOTO_DIR}/photo_{timestamp}.jpg" 126 logger.info(f"Taking photo: {filename}") 127 128 picam2.capture_file(filename) 129 logger.info("Photo taken successfully") 130 except IOError as e: 131 logger.error(f"IO Error while taking photo: {str(e)}") 132 except Exception as e: 133 logger.error(f"Unexpected error while taking photo: {str(e)}") 134 135def run_server(): 136 try: 137 handler = PhotoHandler 138 with socketserver.TCPServer(("", Config.WEB_PORT), handler) as httpd: 139 logger.info(f"Web server started at port {Config.WEB_PORT}") 140 httpd.serve_forever() 141 except Exception as e: 142 logger.error(f"Server error: {str(e)}") 143 144def main(): 145 logger.info("Camera and web server starting") 146 server = None 147 148 try: 149 server = socketserver.TCPServer(("", Config.WEB_PORT), PhotoHandler) 150 server_thread = threading.Thread(target=server.serve_forever, daemon=True) 151 server_thread.start() 152 153 previous_state = GPIO.input(Config.BUTTON_PIN) 154 while True: 155 current_state = GPIO.input(Config.BUTTON_PIN) 156 157 if current_state == False and previous_state == True: 158 logger.info("Button press detected") 159 take_photo() 160 time.sleep(Config.DEBOUNCE_DELAY) 161 162 previous_state = current_state 163 time.sleep(Config.POLL_INTERVAL) 164 165 except KeyboardInterrupt: 166 logger.info("Program stopped by user") 167 except Exception as e: 168 logger.error(f"Unexpected error: {str(e)}") 169 finally: 170 if server: 171 server.shutdown() 172 server.server_close() 173 GPIO.cleanup() 174 logger.info("GPIO cleaned up") 175 176if __name__ == "__main__": 177 main()