This repository has no description
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()