我已经开发了自己的photobooth软件,使用了Raspberry Pi和dslcam,以及Python、TkInter和gPhoto库。photobooth软件非常灵活,因为它使用了一个配置文件,并且支持多种类型的输入按钮和led、实时预览和随机照片库(作为屏幕保护程序)。我也可以操纵相机设置为现场预览和拍摄最后的照片。在
大多数时候软件运行得相当稳定。但是有些事情(通常是晚上一次)我遇到一个无法处理的异常,我无法修复,photobooth软件需要手动重新启动。未处理的异常在拍摄最终照片后出现。由于未经处理的异常随机出现,我无法自己复制它。在
未处理的异常始终相同:
Traceback (most recent call last):
File "/home/pi/photoBooth/scripts/photoBoothGUI.pyw", line 476, in main try: ThreadedClient(Tk()).start()
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1823, in init self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
TclError: no display name and no $DISPLAY environment variable
作为一个快速而不脏的解决方案,我已经尝试将大多数可能引发异常的函数调用放入try-and-catch块中。但这并没有解决上述例外情况。在
这是photoBoothGui.pyw公司名称:
#!/usr/bin/python
# -*- coding: utf-8 -*-
#Imports
import Queue, threading
import sys, os, traceback
import time, datetime, random
from gPhotoWrapper import GPhotoWrapper
from photoUtils import PhotoUtils
from RPi import GPIO
from Tkinter import Tk, Frame, Label, BOTH, Button, CENTER
from tkFont import Font
from PIL import Image, ImageTk, ImageOps
from configparser import ConfigParser
from enum import Enum
from shutil import copyfile
#Class Definitions
class MessageType(Enum):
IMAGE = 0
TEXT = 1
IMAGE_TEXT = 2
class Message():
def __init__(self, mType, text = None, fontSize=None, image=None):
self.mType = mType
self.text = text
self.fontSize = fontSize
self.image = image
class GUI(Frame):
def __init__(self, parent, queue, config):
#Init root window and root queue
Frame.__init__(self, parent)
self.parent = parent
self.queue = queue
#Configure font
self.labelFont = Font(family=config.get('Font','Family'), size=config.get('Font','SizeTitle'))
self.labelFont.configure(weight='bold') if config.getboolean('Font','Bold') else None
#Configure main label
self.mainLabel = Label(self, compound=CENTER, font=self.labelFont, text=config.get('RootWindow','AppTitle'), wraplength=1280)
self.mainLabel.configure(background=config.get('RootWindow','BackgroundColor'))
self.mainLabel.configure(foreground=config.get('RootWindow','ForegroundColor'))
self.mainLabel.pack(fill=BOTH, expand=1)
self.pack(fill=BOTH, expand=1)
#Configure root window
self.parent.title(config.get('RootWindow','AppTitle'))
self.parent.attributes('-fullscreen', config.getboolean('RootWindow','Fullscreen'))
self.config(cursor='none')
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
if msg.mType == MessageType.TEXT:
self.showText(msg.text, msg.fontSize)
self.resetImage()
elif msg.mType == MessageType.IMAGE:
self.showImage(msg.image)
self.resetText()
elif msg.mType == MessageType.IMAGE_TEXT:
self.showImage(msg.image)
self.showText(msg.text, msg.fontSize)
else:
print('Message Type "' + str(msg.mType) + '" not implemented, yet')
except:
pass
def showImage(self, img):
try:
photoImage = ImageTk.PhotoImage(img)
self.mainLabel.configure(image = photoImage)
self.mainLabel.image = photoImage
except:
pass
def showText(self, textToShow, fontSize):
try:
self.mainLabel.configure(text=textToShow)
self.labelFont.configure(size=fontSize)
except:
pass
def resetImage(self):
self.mainLabel.configure(image='')
def resetText(self):
self.mainLabel.configure(text='')
def getMaxImageWidth(self):
return self.mainLabel.winfo_width();
def getMaxImageHeight(self):
return self.mainLabel.winfo_height();
class ClientState(Enum):
BUSY = 0
READY_CAPTURE = 1
class ThreadedClient:
def __init__(self,window):
#Save a reference to root window
self.window = window
#Init config object
self.config = ConfigParser()
try:
if not self.config.read(sys.argv[1]):
raise IOError('File "' + sys.argv[1] + '" not found!')
except IndexError, IOError:
if not self.config.read(os.path.splitext(os.path.basename(sys.argv[0]))[0]+'.config'):
print('Loading config file failed.')
sys.exit()
#Setup GPhoto Wrapper
self.photoWrapper = GPhotoWrapper()
#Setup photo utils
self.photoUtils = PhotoUtils()
#Create queue
self.queue = Queue.Queue()
#Setup the GUI
self.gui = GUI(window, self.queue , self.config)
#Bind keypress event to GUI
self.gui.parent.bind("<Return>", self.readKeypressCapture)
#Start periodic call in GUI
self.periodicCall(self.config.getint('RootWindow','UpdateIntervall'))
#Setup the GPIO
self.setupGPIO()
#Setup Slideshow thread
self.setupThreads()
#Set the application to be ready for taking the first photo
self.setClientState(ClientState.READY_CAPTURE)
def start(self):
self.window.protocol('WM_DELETE_WINDOW', self.close)
self.window.mainloop()
def close(self):
#Cancel background thread
self.threadSlideshow.cancel()
#Reset GPIO pins
GPIO.cleanup()
#Close GUI
self.window.destroy()
def setClientState(self, clientState):
self.state = clientState
def periodicCall(self, intervall):
#Process incoming queue messages
self.gui.processIncoming()
#Restart periodic call every x ms
self.window.after(intervall, self.periodicCall, intervall)
def setupGPIO(self):
#Setup GPIO pin layout
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
bounceTime = self.config.getint('RPi GPIO','Bouncetime')
#Capturing: Setup GPIO pins and wait for capture events
GPIO.setup(self.config.getint('RPi GPIO','ButtonCaptureGPIO'), GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(self.config.getint('RPi GPIO','ButtonCaptureGPIO'), GPIO.FALLING, callback=self.readButtonCapture, bouncetime=bounceTime)
if self.config.getboolean('RPi GPIO','EnableRadioCapture'):
GPIO.setup(self.config.getint('RPi GPIO','ButtonCaptureRadioGPIO'), GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.add_event_detect(self.config.getint('RPi GPIO','ButtonCaptureRadioGPIO'), GPIO.FALLING, callback=self.readButtonCapture, bouncetime=bounceTime)
#LED: Setup GPIO pin and initialize LED status to high signal
if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
GPIO.setup(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.OUT)
GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.HIGH)
def setupThreads(self):
if self.config.getboolean('Slideshow','EnableSlideshow'):
self.threadSlideshow = RepeatingTimer(interval=self.config.getint('Slideshow','DisplayDuration'), target=self.showRandomImage)
else:
self.threadSlideshow = RepeatingTimer(interval=self.config.getint('Slideshow','DisplayDuration'), target=self.dummy)
def readButtonCapture(self, channel):
threading.Thread(target=self.captureImageButtonThread, args=[channel]).start()
def readKeypressCapture(self, key):
threading.Thread(target=self.captureImageThread).start()
def captureImageButtonThread(self, channel):
try:
#Check the button to be pressed curtain amount of time
timeThreshold = time.time() + (self.config.getint('RPi GPIO','Threshold')/1000)
while timeThreshold > time.time():
if GPIO.input(channel): return
self.captureImageThread()
except:
#Show Error Message
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamFailed'), fontSize=self.config.getint('Font','SizeMessage')))
def captureImageThread(self):
if self.state == ClientState.BUSY: return
try:
#Stop background threads (LED and Slideshow)
self.threadSlideshow.cancel()
if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.LOW)
#Capture image
self.captureImage()
#Restart background threads (LED and Slideshow)
self.threadSlideshow.startAfter(self.config.getint('Slideshow','StartTime'))
if self.config.getboolean('RPi GPIO','EnableCaptureLED'):
GPIO.output(self.config.getint('RPi GPIO','LedCaptureGPIO'), GPIO.HIGH)
except:
#Show Error Message
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamFailed'), fontSize=self.config.getint('Font','SizeMessage')))
def captureImage(self):
#Check whether application is ready to capture new image
if self.state == ClientState.BUSY: return
#Switch applications' state
self.setClientState(ClientState.BUSY)
#Read config values for capture mode
fontSizeMessage = self.config.getint('Font','SizeMessage')
fontSizeCountdown = self.config.getint('Font','SizeCountdown')
isLivePreview = self.config.getboolean('GPhotoParameter','LivePreview')
#Establish connection to camera
if self.photoWrapper.connect():
#Start live preview
if isLivePreview:
isoKey = self.config.get('GPhotoParameter','IsoSpeed')
isoValuePrev = self.config.get('GPhotoParameter','IsoSpeedValuePreview')
apertureKey = self.config.get('GPhotoParameter','Aperture')
apertureValuePrev = self.config.get('GPhotoParameter','ApertureValuePreview')
apertureValueCapt = isoValueCapt = None
if self.config.getboolean('GPhotoParameter','FixCaptureSettings'):
apertureValueCapt = self.config.get('GPhotoParameter','ApertureValueCapture')
isoValueCapt = self.config.get('GPhotoParameter','IsoSpeedValueCapture')
if not self.photoWrapper.startPreview(isoKey, isoValuePrev, isoValueCapt, apertureKey, apertureValuePrev, apertureValueCapt):
#Close connection to camera
self.photoWrapper.disconnect()
#Show Error Message
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
#Switch applications' state
self.setClientState(ClientState.READY_CAPTURE)
return
#Start countdown for posing
countdown = self.config.getfloat('Photos','Countdown')
stepSize = self.config.getfloat('RootWindow','UpdateIntervall')/1000
while countdown > 0:
number = int(countdown)
textMsg = number if number>0 else self.config.get('Text','CamPhotoShot')
textSize = fontSizeCountdown if number>0 else fontSizeMessage
if isLivePreview:
prevImage = self.photoWrapper.capturePreview()
if prevImage != False: self.queue.put(Message(mType=MessageType.IMAGE_TEXT, text=textMsg, fontSize=textSize, image=ImageOps.mirror(prevImage)))
else: self.queue.put(Message(mType=MessageType.TEXT, text=textMsg, fontSize=textSize))
else: self.queue.put(Message(mType=MessageType.TEXT, text=textMsg, fontSize=textSize))
countdown -= stepSize
time.sleep(stepSize)
#Stop live preview
if not self.photoWrapper.stopPreview():
#Close connection to camera
self.photoWrapper.disconnect()
#Show Error Message
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
#Switch applications' state
self.setClientState(ClientState.READY_CAPTURE)
return
#Capture image
camFilePath = self.photoWrapper.captureImage()
if camFilePath:
#Download image
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoDownload'), fontSize=fontSizeMessage))
try:
unixTimeStamp = self.photoWrapper.getConfigValue(self.config.get('GPhotoParameter','CameraDateTime'))
localFilePath = os.path.join(self.config.get('Photos','PathLocal'), self.fileName(unixTimeStamp))
except:
localFilePath = None
if (localFilePath and self.photoWrapper.downloadImage(camFilePath,localFilePath)):
try:
#Process image
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoProcessing'), fontSize=fontSizeMessage))
origImage = Image.open(localFilePath)
#Resize image in two steps (best trade-off between speed and quality)
scaledImage = self.photoUtils.resizeAspectRatio(origImage, self.gui.getMaxImageHeight())
previewImage = scaledImage.copy()
#Save (resized) image to webservers' photo directory
if self.config.getboolean('Photos','CopyToWebServer'):
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoSave'), fontSize=fontSizeMessage))
targetOrig = os.path.join(self.config.get('Photos','PathWebServerOrig'), os.path.basename(localFilePath))
targetThumb = os.path.join(self.config.get('Photos','PathWebServerThumb'), os.path.basename(localFilePath))
if self.config.getboolean('Photos','SaveScaledImage'):
scaledImage = self.photoUtils.resizeFixed(origImage, self.config.getint('Photos','OrigHeight'), self.config.getint('Photos','OrigWidth'))
scaledImage.save(targetOrig, quality=self.config.getint('Photos','OrigQuality'))
else: copyfile(localFilePath, targetOrig)
scaledImage = self.photoUtils.resizeFixed(scaledImage, self.config.getint('Photos','ThumbHeight'), self.config.getint('Photos','ThumbWidth'))
scaledImage.save(targetThumb)
#Show image
self.queue.put(Message(mType=MessageType.IMAGE, image=previewImage))
except:
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoProcessFailed'), fontSize=fontSizeMessage))
else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamPhotoDownloadFailed'), fontSize=fontSizeMessage))
else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotReady'), fontSize=fontSizeMessage))
else: self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','CamNotDetected'), fontSize=fontSizeMessage))
#Close connection to camera
self.photoWrapper.disconnect()
#Switch applications' state
self.setClientState(ClientState.READY_CAPTURE)
def showRandomImage(self):
#Check client state
if self.state == ClientState.BUSY: return False
self.setClientState(ClientState.BUSY)
#Show loading message
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('Text','PleaseWait'), fontSize=self.config.get('Font','SizeMessage')))
#Get random image from slideshow directory
if (self.config.getboolean('Slideshow','EnableAdvertisements')):
if(random.randint(0,3) > 0):
filePath = self.config.get('Slideshow','SlideShowFolder')
else:
filePath = self.config.get('Slideshow','AdvertisementFolder')
else:
filePath = self.config.get('Slideshow','SlideShowFolder')
if os.listdir(filePath):
try:
fileName = random.choice([x for x in os.listdir(filePath) if os.path.isfile(os.path.join(filePath, x))])
filePathAndName = os.path.join(filePath, fileName)
#Resize image in two steps (best trade-off between speed and quality)
scaledImage = self.photoUtils.resizeAspectRatio(Image.open(filePathAndName), self.gui.getMaxImageHeight())
#Show Image
self.queue.put(Message(mType=MessageType.IMAGE, image=scaledImage))
except:
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('RootWindow','AppTitle'), fontSize=self.config.get('Font','SizeTitle')))
#Change Client State
self.setClientState(ClientState.READY_CAPTURE)
return True
def reset(self):
if not self.state == ClientState.READY_CAPTURE: return
self.queue.put(Message(mType=MessageType.TEXT, text=self.config.get('RootWindow','AppTitle'), fontSize=self.config.get('Font','SizeTitle')))
def fileName(self, unixTimeStamp):
prefix = self.config.get('Photos','Prefix')
return prefix + time.strftime('%Y%m%d_%H%M%S', time.localtime(unixTimeStamp)) + '.jpg'
def dummy():
return False
class RepeatingTimer(object):
def __init__(self, interval, target, onCancel=None, *args, **kwargs):
self.interval = interval
self.target = target
self.onCancel = onCancel
self.args = args
self.kwargs = kwargs
self.timer = None
self.isAlive = False
def callback(self):
if self.target(*self.args, **self.kwargs):
self.isAlive = False
self.startAfter(self.interval)
else: self.isAlive = False
def cancel(self):
self.isAlive = False
if not self.timer == None: self.timer.cancel()
if not self.onCancel == None: self.onCancel(*self.args, **self.kwargs)
def start(self):
self.startAfter(self.interval)
def startAfter(self, interval):
if self.isAlive: return
self.timer = threading.Timer(interval, self.callback)
self.isAlive = True
self.timer.start()
class Logger(file):
def __init__(self, name, mode = 'w'):
self = file.__init__(self, name, mode)
def writeLine(self, string):
self.writelines(string + '\n')
return None
#Main Function
def main():
while(True):
try: ThreadedClient(Tk()).start()
except:
#Get information from unhandled exception
exc_type, exc_value, exc_traceback = sys.exc_info()
exceptionInfo = repr(traceback.format_exception(exc_type, exc_value,exc_traceback))
#Output unhandled exception to error.log file
f = Logger(os.path.dirname(sys.argv[0])+'/errors.log','a')
f.writeLine('')
f.writeLine('***************************************************')
f.writeLine('** EXCEPTION **')
f.writeLine('***************************************************')
for line in traceback.format_exception(exc_type, exc_value,exc_traceback): f.writeLine(line)
f.close()
else: break
if __name__ == '__main__':
main()
如果能得到一些如何修复未处理的异常的提示,那就太好了。在
目前没有回答
相关问题 更多 >
编程相关推荐