I am making what is essentially a "See and Say", but running on a Raspberry Pi. I am using an Adafruit MPR121 capacitive touch sensor to provide the "keyboard" input to control the sequential display of a set of photos, then once the desired photo is displayed, a different "key" is touched to play the associated audio, for example, scroll a list of animal photos, select the duck, then play the sound of a duck quacking.
我做的本质上是“看着说”,但运行在覆盆子圆周率。我使用Adafruit MPR121电容式触摸传感器来提供“键盘”输入,以控制一组照片的顺序显示,然后一旦显示所需的照片,就会触摸不同的“键”来播放相关音频,例如,滚动动物照片列表,选择鸭子,然后播放鸭子嘎嘎的声音。
I am using Adafruit's CircuitPython library for the MPR121 via Blinka, Pillow, and tkinter. I know little to nothing of the last two, but have managed to glom together bits of examples I found online to make something that sort of works to scroll photos.
我通过Blinka、Pillow和Tkinter在MPR121上使用Adafruit的CircuitPython库。我对最后两个几乎一无所知,但我设法收集了一些我在网上找到的例子,制作了一种可以滚动照片的东西。
Based on the time taken to get to that point, I want to ask here if anyone has some suggestions to keep from going down a blind alley, and to see if there is a better way than what I have currently done.
基于达到这一点所花费的时间,我想在这里问一问,是否有人有什么建议可以避免走进死胡同,看看是否有比我目前所做的更好的方法。
Edits: Changing the delay value of root.after() from 500 to 150 has greatly improved response time - it's long enough to act as a debounce, but not so long that cap touch input is lost.
编辑:将root.After()的延迟值从500更改为150可以极大地改进响应时间-它的长度足以充当去抖动,但又不会太长,以至于帽触摸输入丢失。
I got it to work. I'm still poen to suggestions. I left the issues here in case anyone else has similar issues.
我让它工作了。我仍然倾向于建议。我把问题放在这里,以防其他人也有类似的问题。
I tried using playsound for audio out. I get a warning when it starts:
我试着用PlaySound进行音频输出。当它开始时,我会收到警告:
playsound is relying on another python subprocess. Please use pip install pygobject
if you want playsound to run more efficiently.
When I install pygobject, the installer says it is already installed.
当我安装pygobject时,安装程序告诉我它已经安装了。
When playsound() is called, I get an error:
当调用playSound()时,我得到一个错误:
Traceback (most recent call last):
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 261, in <module>
playsound(argv[1])
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 163, in _playsoundNix
gi.require_version('Gst', '1.0')
File "/usr/lib/python3/dist-packages/gi/__init__.py", line 126, in require_version
raise ValueError('Namespace %s not available' % namespace)
ValueError: Namespace Gst not available
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.9/tkinter/__init__.py", line 1892, in __call__
return self.func(*args)
File "/usr/lib/python3.9/tkinter/__init__.py", line 814, in callit
func(*args)
File "/home/charley/Documents/DogIF/DogIF.py", line 76, in scanTouch
sound4img()
File "/home/charley/Documents/DogIF/DogIF.py", line 59, in sound4img
playsound('/usr/share/scratch/Media/Sounds/Instruments/Dijjeridoo.mp3')
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 254, in <lambda>
playsound = lambda sound, block = True: _playsoundAnotherPython('/usr/bin/python3', sound, block, macOS = False)
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 229, in _playsoundAnotherPython
t.join()
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 218, in join
raise self.exc
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 211, in run
self.ret = self._target(*self._args, **self._kwargs)
File "/home/charley/.local/lib/python3.9/site-packages/playsound.py", line 226, in <lambda>
t = PropogatingThread(target = lambda: check_call([otherPython, playsoundPath, _handlePathOSX(sound) if macOS else sound]))
File "/usr/lib/python3.9/subprocess.py", line 373, in check_call
raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['/usr/bin/python3', '/home/charley/.local/lib/python3.9/site-packages/playsound.py', '/usr/share/scratch/Media/Sounds/Instruments/Dijjeridoo.mp3']' returned non-zero exit status 1.
so I used preferredsoundplayer instead. It works.
所以我改用了首选音响播放器。它起作用了。
Here's the code:
代码如下:
import tkinter as tk
from tkinter import *
from PIL import Image
from PIL import ImageTk
import time
import board
import busio
# Import MPR121 module.
import adafruit_mpr121
from preferredsoundplayer import playsound
# Create I2C bus.
i2c = busio.I2C(board.SCL, board.SDA)
# Create MPR121 object.
mpr121 = adafruit_mpr121.MPR121(i2c)
# adjust window
root=tk.Tk()
root.geometry("800x500")
# loading the images
img1=ImageTk.PhotoImage(Image.open("/home/charley/Documents/Components/SBC/RPi/GPIO-Pinout-Diagram-2.png").resize((800,500)))
img2=ImageTk.PhotoImage(Image.open("/home/charley/Pictures/Eastern_Grey_Squirrel.jpg").resize((800,500)))
img3=ImageTk.PhotoImage(Image.open("/home/charley/Pictures/sqrl.jpg").resize((800,500)))
l=Label(root, text="Apparently not the window title")
l.pack()
x = 1
# function to change to next image
def move(inc):
global x
x = x+inc
if x >= 4:
x = 1
if x <= 0:
x = 3
if x == 1:
l.config(image=img1)
elif x == 2:
l.config(image=img2)
elif x == 3:
l.config(image=img3)
print("x = {} ".format(x))
def sound4img():
if x == 1:
playsound('/usr/share/scratch/Media/Sounds/Instruments/Dijjeridoo.mp3')
elif x == 2:
playsound('/usr/share/scratch/Media/Sounds/Instruments/Dijjeridoo.mp3')
elif x == 3:
playsound('/usr/share/scratch/Media/Sounds/Instruments/Dijjeridoo.mp3')
# Loop forever testing each input and printing when they're touched.
# while True:
# Loop through all 12 inputs (0-11).
def scanTouch():
for i in range(12):
# Call is_touched and pass it the number of the input. If it's touched
# it will return True, otherwise it will return False.
if mpr121[i].value:
print("Input {} touched!".format(i))
if i == 0:
move(1)
if i == 1:
move(-1)
if i == 2:
sound4img()
root.after(150, scanTouch) # after(delay_ms, callback=None, *args)
root.after(50, scanTouch)
root.mainloop()
更多回答
First, I always sugest an OOP aproach when working with tk. This also helps with your move
function so you can renounce the usage of global
for x
and instad make it to a class variable. Secondly in regards to the scaling, I would look into setting a specific size for the label ore even better asign it to the root you created and use the sticky
parameter: l=Label(root, sticky="nsew")
Lastly, maby the response time gets better if you display two buttons instead of constantly scanning for touch input or try a callback function for the root element?
首先,在与tk合作时,我总是建议使用面向对象的方法。这也有助于您的MOVE函数,因此您可以放弃对x使用global,而将其作为一个类变量。其次,关于缩放,我会考虑为标签设置一个特定的大小,或者更好地将其赋给您创建的根,并使用粘滞参数:L=Label(Root,Sticky=“nsew”)最后,如果您显示两个按钮而不是不断扫描触摸输入或尝试为根元素尝试回调函数,响应时间会更好?
The finished product will have no keyboard and no mouse, so I'm stuck with scanning the capacitive touch sensor since it is the only input.
成品将没有键盘和鼠标,所以我只能扫描电容式触摸传感器,因为它是唯一的输入。
优秀答案推荐
DISCLAIMER: I did not test if the "keyboard" implementation works or if it is more performant
免责声明:我没有测试“键盘”实现是否工作,或者它的性能是否更好
In regards to this guide the Adafruit MPR121 can be used for keyboard input on the raspberry pi. To do so the following dependencies are necessary:
就本指南而言,Adafruit MPR121可用于覆盆子圆周率的键盘输入。要做到这一点,必须具备以下依赖关系:
sudo apt-get update
sudo apt-get install libudev-dev
sudo pip3 install python-uinput
sudo pip3 install adafruit-circuitpython-mpr121
After those are installed on your raspberry pi in the MPR121 library examples folder there should be a pi_keyboard.py script with a section of a KEY_MAPPING
dictionary:
在MPR121 library examples文件夹中的raspberry pi上安装这些之后,应该有一个pi_keyboard.py脚本,其中包含KEY_MAPPING字典的一部分:
KEY_MAPPING = {
0: uinput.KEY_UP,
1: uinput.KEY_DOWN,
2: uinput.KEY_LEFT,
3: uinput.KEY_RIGHT,
4: uinput.KEY_B,
5: uinput.KEY_A,
6: uinput.KEY_ENTER,
7: uinput.KEY_SPACE,
}
You could either change this dictionary in a way that it fits your inputs for left right and enter like this (or change the inputs of the sensors to match the dict):
您可以更改此词典,使其适合您的左右输入,然后按如下方式输入(或者更改传感器的输入以匹配词典):
KEY_MAPPING = {
0: uinput.KEY_RIGHT, # your right sensor
1: uinput.KEY_LEFT, # your left sensor
2: uinput.KEY_ENTER, # your "sound" sensor
3: uinput.KEY_UP,
4: uinput.KEY_B,
5: uinput.KEY_A,
6: uinput.KEY_DOWN,
7: uinput.KEY_SPACE,
}
You then can run this script in the back to use the keybord input in your main script as callback. Do so by running sudo python keyboard.py &
and note the response ID to later kill the process again with e.g. sudo kill 2251
.
然后,您可以在后台运行此脚本,以使用主脚本中的keybord输入作为回调。为此,请运行sudo python keyboard.py&并记下响应ID,以便稍后再次使用sudo kill 2251终止该进程。
Now to the main script:
现在让我们来看看主剧本:
As we have the key mapping active for your touch sensors we can ommit the part of your script scanning for touches. I tried to make your script more generic, so you can add pictures and sounds and it wil still work. Also this makes it a bit more pythonic I think. Furthermore, I refactored your code into a OOP approach:
由于我们为您的触摸传感器激活了键映射,因此我们可以省略您的脚本扫描触摸的部分。我试着让你的脚本更通用,这样你就可以添加图片和声音,它仍然可以工作。此外,我认为这也让它变得更具蟒蛇色彩。此外,我将您的代码重构为OOP方法:
from tkinter import * # if you import everything from tkinter you dont need the as tk line
from PIL import Image, ImageTk # cleaner to have both imports in one line
from preferredsoundplayer import playsound
from glob import glob # use this module to get a list of paths
# create App class
class SlidshowApp(Tk):
def __init__(self):
super().__init__() # instantiate the Tk class of wich you inherit
# set Window title and geometry
self.title('Animal Slideshow') # title is set for the root window, not in a Label text
self.geometry('800x500')
# instantiate a empty list to add all the pictures
self.imagelist = []
# Load all image paths from your image folder into a list and iterate over it
for path in glob('/home/charley/Pictures/*.jpg'):
self.imagelist.append(ImageTk.PhotoImage(Image.open(path).resize((800, 500))))
# also glob all the sound paths (make sure the sounds are in the same order as the pictures!)
self.soundlist = glob('/usr/share/scratch/Media/Sounds/Instruments/*.mp3')
# instantiate the Label as a container element for the pictures
self.img_container = Label(self)
self.img_container.pack()
# counter as instance variable
self.x = 1
# bind keystrokes (your sensors) to the root to trigger move or sound4image
self.bind('<Left>', lambda event: self.move(inc=-1))
self.bind('<Right>', lambda event: self.move(inc=1))
# use the lambda function to play the corresponding sound directly
self.bind('<Return>', lambda event: playsound(self.soundlist(self.x)))
# function to change to next/previous image
def move(self, inc):
self.x += inc
if self.x >= len(self.imagelist): # index out of range, start from 0
self.x = 0
elif self.x < 0: # index out of range, start from last index
self.x = len(self.imagelist) - 1
# instead of assigning a image to an x value rather use x as an index for the list we created
self.img_container.config(image=self.imagelist[self.x])
app = SlidshowApp()
if __name__ == '__main__':
app.mainloop()
In terms of the problem with playsound()
就PlaySound()的问题而言
Maby it is better to use a more well maintained library like pygame which might already be preinstalled on your raspberry pi. But consider that this needs to use the standard audio output.
Maby最好使用一个维护得更好的库,比如pyGame,它可能已经预装在你的树莓pi上了。但考虑到这需要使用标准音频输出。
This post shows how to use it and discusses alternatives.
这篇文章展示了如何使用它,并讨论了替代方案。
So after checking if pygame is installed and updating it you can import the mixer object from pygame like from pygame import mixer
. Then change the part in my script, where the sound paths are loaded into a list to this:
因此,在检查是否安装了pyGame并对其进行更新后,您可以从pyGame导入搅拌器对象,就像从pyGame导入搅拌器一样。然后在我的脚本中将声音路径加载到列表中的部分更改为:
# also glob all the sound paths (make sure the sounds are in the same order as the pictures!)
mixer.init()
for s_path in glob('/usr/share/scratch/Media/Sounds/Instruments/*.mp3'):
self.soundlist.append(mixer.Sound(s_path))
Then change the binding for the sound back to self.bind('<Return>', lambda event: self.sound4image())
and write the function as method of the class like this:
然后将声音的绑定改回self.ind(‘
’,lambda Event:self.sound4Image()),并将函数编写为类的方法,如下所示:
# funbction to play the sound
def sound4image(self):
mixer.Sound.play(self.soundlist[self.x])
mixer.music.stop()
更多回答
我是一名优秀的程序员,十分优秀!