目录
- 背景
- 实验环境
- 问题记录
- 1、`sudo pip install xxx`报错:error: externally-managed-environment
- 2、`sudo apt-get install python-smbus`报错:E: Package 'python-smbus' has no installation candidate
- 3、`pip install`执行缓慢或中断失败
- 4、使用Pillow库报错:ImportError: cannot import name '_imagingft' from 'PIL'
- 5、使用Pillow库报错:ImportError: The _imagingft C module is not installed
- 点亮效果
背景
几年前买的树莓派3B+,吃灰了一段时间,拿出来玩一玩。把系统版本更新到了目前最新的 raspios bookworm(Debian 12)版本,旧版系统上有正常点亮的SSD1306驱动128x64 oled屏和python程序,准备恢复点亮的,结果遇到各种问题,就记录总结一下。
实验环境
时间:2024年8月25日
硬件:树莓派3B+
系统版本:Raspberry Pi OS Lite(32位) bookworm
问题记录
2019年,当时参考 树莓派实验室 - 在树莓派上使用 SSD1306 OLED 屏幕 这篇教程,在低版本系统上成功点亮。
这次,操作步骤还是参考上面的那片教程,但随着Debian系统和python版本更新,有些地方不再适用了。
1、sudo pip install xxx
报错:error: externally-managed-environment
具体错误输出如下:
$ sudo python -m pip install --upgrade pip setuptools wheel
error: externally-managed-environment
× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
python3-xyz, where xyz is the package you are trying to
install.
If you wish to install a non-Debian-packaged Python package,
create a virtual environment using python3 -m venv path/to/venv.
Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
sure you have python3-full installed.
For more information visit http://rptl.io/venv
note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.
解决方法: Raspberry Pi OS系统版本升级后,包管理优化的一些改变。按照提示,配置 python 虚拟环境(venv
)即可,上面错误信息中就有链接,教程网上也随便能搜到,不再赘述。
为了方便以后使用,我在.bashrc最后添加了激活 python 虚拟环境的命令,这样就每次 ssh 打开的远程终端都默认是虚拟环境:
~/.bashrc:
# 省略默认内容...
source /home/xxuser/rpi-py-env/bin/activate
2、sudo apt-get install python-smbus
报错:E: Package ‘python-smbus’ has no installation candidate
解决方法: 新版本系统中,通过apt安装python模块的方式不再适用,通过pip install smbus
方式安装,其他python中用到的依赖库同理。
pip install smbus
# python中用到的其他依赖模块...
3、pip install
执行缓慢或中断失败
**解决方法:**更换国内pip镜像源,参考配置:
/home/xxuser/.config/pip/pip.conf
[global]
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
extra-index-url=
https://mirrors.aliyun.com/pypi/simple/
https://pypi.mirrors.ustc.edu.cn/simple/
[install]
trusted-host=
pypi.tuna.tsinghua.edu.cn
mirrors.aliyun.com
pypi.mirrors.ustc.edu.cn
4、使用Pillow库报错:ImportError: cannot import name ‘_imagingft’ from ‘PIL’
解决方法: 2024年8月这个时间,pip install Pillow
默认安装的最新版本是pillow 10.4.0,pillow版本和python版本不兼容会报错,经过逐一验证,回退安装低版本Pillow-8.4.0,避免该报错,但是有其他错误,下面会说到。
pip uninstall Pillow
pip install "Pillow<9.0.0"
5、使用Pillow库报错:ImportError: The _imagingft C module is not installed
解决方法: 先卸载Pillow,安装必要的依赖库,再加上--no-cache-dir
参数重新安装Pillow
pip uninstall Pillow
sudo apt-get install libjpeg-dev zlib1g-dev
pip install "Pillow<9.0.0" --no-cache-dir
点亮效果
我这里使用自制的OLED屏模块,扩展连接DHT11传感器采集温湿度。各种信息采集用到了psutil
和Adafruit_DHT
这两个Python库,通过pip install安装即可。
在/etc/rc.local的exit之前添加上开机启动的执行命令:
# ...
# 使用Python虚拟环境,避免找不到依赖库
/home/shenyong/rpi-py-env/bin/python /home/shenyong/stats.py
exit 0
点亮脚本记录
基于 Adafruit_Python_SSD1306/example/stats.py 进行的定制修改。
# Copyright (c) 2017 Adafruit Industries
# Author: Tony DiCola & James DeVito
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# 指定虚拟环境的路径
import sys
sys.path.insert(0, '/home/shenyong/rpi-py-env/lib/python3.11/site-packages')
import time
import Adafruit_GPIO.SPI as SPI
import Adafruit_SSD1306
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
import subprocess
import psutil
######## for DHT11 ########
import smbus
import sys
import threading
import Adafruit_DHT
DHT_PIN = 4
SAMPLE_INTERVAL_SEC = 5
init_ok = False
shared_lock = threading.Lock()
envStr = ""
######## for DHT11 end ########
# Raspberry Pi pin configuration:
RST = None # on the PiOLED this pin isnt used
# Note the following are only used with SPI:
DC = 23
SPI_PORT = 0
SPI_DEVICE = 0
# Beaglebone Black pin configuration:
# RST = 'P9_12'
# Note the following are only used with SPI:
# DC = 'P9_15'
# SPI_PORT = 1
# SPI_DEVICE = 0
# 128x32 display with hardware I2C:
#disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST)
# 128x64 display with hardware I2C:
disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST)
# Note you can change the I2C address by passing an i2c_address parameter like:
# disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST, i2c_address=0x3C)
# Alternatively you can specify an explicit I2C bus number, for example
# with the 128x32 display you would use:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, i2c_bus=2)
# 128x32 display with hardware SPI:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))
# 128x64 display with hardware SPI:
# disp = Adafruit_SSD1306.SSD1306_128_64(rst=RST, dc=DC, spi=SPI.SpiDev(SPI_PORT, SPI_DEVICE, max_speed_hz=8000000))
# Alternatively you can specify a software SPI implementation by providing
# digital GPIO pin numbers for all the required display pins. For example
# on a Raspberry Pi with the 128x32 display you might use:
# disp = Adafruit_SSD1306.SSD1306_128_32(rst=RST, dc=DC, sclk=18, din=25, cs=22)
def get_dht11_sensor():
global init_ok
global shared_lock
global disp
global envStr
while True:
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, DHT_PIN)
init_ok = True
shared_lock.acquire()
envStr = 'T:{:.0f}°C H:{:.0f}% '.format(temperature, humidity)
shared_lock.release()
time.sleep(SAMPLE_INTERVAL_SEC)
if __name__ == '__main__':
now = time.strftime('%m/%d %H:%M:%S', time.localtime(time.time()))
# Initialize library.
disp.begin()
# Clear display.
disp.clear()
disp.display()
# Create blank image for drawing.
# Make sure to create image with mode '1' for 1-bit color.
width = disp.width
height = disp.height
image = Image.new('1', (width, height))
# Get drawing object to draw on image.
draw = ImageDraw.Draw(image)
# Draw a black filled box to clear the image.
draw.rectangle((0,0,width,height), outline=0, fill=0)
# Draw some shapes.
# First define some constants to allow easy resizing of shapes.
padding = 0
top = padding
bottom = height-padding
# Move left to right keeping track of the current x position for drawing shapes.
x = 0
# Load default font.
# font = ImageFont.load_default()
font_size = 12
font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Thin.ttf', font_size)
# font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Light.ttf', font_size)
# font = ImageFont.truetype('/home/shenyong/Adafruit_Python_SSD1306/examples/JetBrainsMono-Medium.ttf', font_size)
# Alternatively load a TTF font. Make sure the .ttf font file is in the same directory as the python script!
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
# font = ImageFont.truetype('Minecraftia.ttf', 8)
rpi_logo = Image.open('/home/shenyong/Adafruit_Python_SSD1306/examples/rpi.ppm').convert('1')
# draw.text((0, top), "Raspberry Pi 3B+", font=font, fill=255)
# draw.text((0, top + font_size + 1), "Initializing...", font=font, fill=255)
disp.image(rpi_logo)
disp.display()
try:
threading.Thread(target=get_dht11_sensor).start()
except:
print("Error: unable to start thread")
lastIdle = 0
lastTotal = 0
while True:
if (init_ok):
# Draw a black filled box to clear the image.
draw.rectangle((0,0,width,height), outline=0, fill=0)
# Shell scripts for system monitoring from here : https://unix.stackexchange.com/questions/119126/command-to-display-memory-usage-disk-usage-and-cpu-load
cmd = "hostname -I | cut -d\' \' -f1"
IPDesc = subprocess.check_output(cmd, shell = True )
IP = "IP: " + str(IPDesc, encoding = "utf-8")
cpu_percent = psutil.cpu_percent(interval=1)
CPU = 'CPU: {:.1f}%'.format(cpu_percent)
# cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %.2f%%\", $3,$2,$3*100/$2 }'"
#cmd = "free -m | awk 'NR==2{printf \"Mem: %s/%sMB %d%\", $3,$2,$3*100/$2 }'"
#MemUsage = subprocess.check_output(cmd, shell = True )
mem_info = psutil.virtual_memory()
memTotalMB = mem_info.total/1024/1024
memUsedMB = (mem_info.total - mem_info.available)/1024/1024
MemUsage = 'Mem: {:d}M/{:d}M {:d}%'.format(int(memUsedMB), int(memTotalMB), int(mem_info.percent))
cmd = "df -h / | awk 'NR==2{print $3\"/\"$2\" \"$5}'"
diskDesc = subprocess.check_output(cmd, shell = True )
Disk = 'Disk: {}'.format(str(diskDesc, encoding = "utf-8"))
# disk_usage = psutil.disk_usage('/')
# diskTotalGB = disk_usage.total/1024/1024/1024
# diskUsedGB = disk_usage.used/1024/1024/1024
# Disk = 'Disk: {:.1f}/{:d}GB {}%'.format(diskUsedGB, int(diskTotalGB), int(disk_usage.percent))
nowTime = time.strftime('%H:%M', time.localtime(time.time()))
# Write text.
line = 0
draw.text((x, top + font_size * line + 1), IP, font=font, fill=255)
line += 1
draw.text((x, top + font_size * line + 1), CPU, font=font, fill=255)
line += 1
draw.text((x, top + font_size * line + 1), MemUsage, font=font, fill=255)
line += 1
draw.text((x, top + font_size * line + 1), Disk, font=font, fill=255)
shared_lock.acquire()
line += 1
lineStr = '{} {}'.format(nowTime, envStr)
draw.text((0, top + font_size * line + 1), lineStr, font=font, fill=255)
shared_lock.release()
disp.image(image)
disp.display()
time.sleep(2)