oled_system_monitor_final.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. OLED系统监控器 - 树莓派4B专用版
  5. 功能:触摸唤醒(GPIO18->1M电阻->金属片->GPIO24)+ 30秒自动熄屏
  6. """
  7. import time
  8. import socket
  9. import psutil
  10. from datetime import datetime
  11. import RPi.GPIO as GPIO
  12. import sys
  13. # 全局变量
  14. DRAW_OBJECT = None
  15. class I2CDevice:
  16. def __init__(self, address, busnum=1):
  17. import smbus2 # 树莓派4B推荐使用smbus2
  18. self._address = address
  19. self._bus = smbus2.SMBus(busnum)
  20. def write8(self, register, value):
  21. self._bus.write_byte_data(self._address, register, value)
  22. def writeList(self, register, data):
  23. self._bus.write_i2c_block_data(self._address, register, data)
  24. class SSD1306Final:
  25. """SSD1306驱动(树莓派4B优化版)"""
  26. # SSD1306寄存器地址
  27. SETCONTRAST = 0x81
  28. DISPLAYALLON_RESUME = 0xA4
  29. NORMALDISPLAY = 0xA6
  30. DISPLAYOFF = 0xAE
  31. DISPLAYON = 0xAF
  32. SETDISPLAYOFFSET = 0xD3
  33. SETCOMPINS = 0xDA
  34. SETVCOMDETECT = 0xDB
  35. SETDISPLAYCLOCKDIV = 0xD5
  36. SETPRECHARGE = 0xD9
  37. SETMULTIPLEX = 0xA8
  38. SETSTARTLINE = 0x40
  39. MEMORYMODE = 0x20
  40. COLUMNADDR = 0x21
  41. PAGEADDR = 0x22
  42. COMSCANDEC = 0xC8
  43. SEGREMAP = 0xA0
  44. CHARGEPUMP = 0x8D
  45. def __init__(self, width=128, height=64, address=0x3C):
  46. global DRAW_OBJECT
  47. print("=" * 60)
  48. print("SSD1306Final 初始化开始...")
  49. print(f"设备: 树莓派4B专用优化")
  50. print(f"分辨率: {width}x{height}")
  51. print(f"I2C地址: 0x{address:02X}")
  52. print("=" * 60)
  53. self.width = width
  54. self.height = height
  55. self.address = address
  56. self._device = None
  57. self.buffer = [0] * (self.width * self.height // 8)
  58. self.image = None
  59. self.fonts = None
  60. # 1. 检查PIL库
  61. self._check_pil()
  62. # 2. 初始化draw对象
  63. self._force_init_draw()
  64. # 3. 初始化I2C设备(树莓派4B优化)
  65. self._init_i2c(address)
  66. # 4. 初始化SSD1306硬件
  67. if self._device:
  68. self._initialize_hardware()
  69. # 5. 加载字体
  70. self._load_fonts()
  71. print("SSD1306Final 初始化完成!")
  72. print(f"draw对象状态: {'✅ 可用' if self.draw else '❌ 不可用'}")
  73. print(f"I2C设备状态: {'✅ 连接' if self._device else '❌ 未连接'}")
  74. print("=" * 60)
  75. def _check_pil(self):
  76. """检查PIL库"""
  77. global DRAW_OBJECT
  78. try:
  79. from PIL import Image, ImageDraw, ImageFont
  80. self.pil_available = True
  81. self.Image = Image
  82. self.ImageDraw = ImageDraw
  83. self.ImageFont = ImageFont
  84. print(" PIL库检查通过")
  85. except ImportError as e:
  86. self.pil_available = False
  87. print(f" PIL库导入失败: {e}")
  88. print(" 正在尝试安装Pillow库...")
  89. import subprocess
  90. try:
  91. subprocess.check_call([sys.executable, "-m", "pip", "install", "pillow"])
  92. from PIL import Image, ImageDraw, ImageFont
  93. self.pil_available = True
  94. self.Image = Image
  95. self.ImageDraw = ImageDraw
  96. self.ImageFont = ImageFont
  97. print(" Pillow库安装成功")
  98. except Exception as e2:
  99. print(f" Pillow库安装失败: {e2}")
  100. def _force_init_draw(self):
  101. """强制初始化draw对象"""
  102. global DRAW_OBJECT
  103. print(" 初始化draw对象...")
  104. if self.pil_available and hasattr(self, 'Image') and hasattr(self, 'ImageDraw'):
  105. try:
  106. self.image = self.Image.new('1', (self.width, self.height))
  107. self.draw = self.ImageDraw.Draw(self.image)
  108. DRAW_OBJECT = self.draw
  109. print(" draw对象创建成功")
  110. except Exception as e:
  111. print(f" PIL创建draw失败: {e}")
  112. self._create_fallback_draw()
  113. else:
  114. print(" 创建备用draw对象")
  115. self._create_fallback_draw()
  116. if not hasattr(self, 'draw') or self.draw is None:
  117. self._create_fallback_draw()
  118. DRAW_OBJECT = self.draw
  119. def _create_fallback_draw(self):
  120. """创建备用draw对象"""
  121. class FallbackDraw:
  122. def text(self, *args, **kwargs):
  123. print(f" 文本绘制: {args}")
  124. def rectangle(self, *args, **kwargs):
  125. print(f" 矩形绘制: {args}")
  126. self.draw = FallbackDraw()
  127. print(" 使用备用draw对象")
  128. def _init_i2c(self, address):
  129. """初始化I2C设备(树莓派4B优化)"""
  130. print(f"🔌 初始化I2C设备 @ 0x{address:02X}...")
  131. try:
  132. self._device = I2CDevice(address)
  133. print(f" I2C设备连接成功")
  134. except Exception as e:
  135. self._device = None
  136. print(f" I2C设备连接失败: {e}")
  137. print(" 请检查I2C是否启用: sudo raspi-config -> 启用I2C")
  138. def _initialize_hardware(self):
  139. """初始化SSD1306硬件"""
  140. if not self._device:
  141. print(" 跳过硬件初始化")
  142. return
  143. print(" 初始化SSD1306硬件...")
  144. try:
  145. init_commands = [
  146. self.DISPLAYOFF,
  147. self.SETDISPLAYCLOCKDIV, 0x80,
  148. self.SETMULTIPLEX, 0x3F,
  149. self.SETDISPLAYOFFSET, 0x00,
  150. self.SETSTARTLINE | 0x00,
  151. self.CHARGEPUMP, 0x14,
  152. self.MEMORYMODE, 0x00,
  153. self.SEGREMAP | 0x01,
  154. self.COMSCANDEC,
  155. self.SETCOMPINS, 0x12,
  156. self.SETCONTRAST, 0xFF,
  157. self.SETPRECHARGE, 0xF1,
  158. self.SETVCOMDETECT, 0x40,
  159. self.DISPLAYALLON_RESUME,
  160. self.NORMALDISPLAY,
  161. self.DISPLAYON
  162. ]
  163. for cmd in init_commands:
  164. self.command(cmd)
  165. time.sleep(0.01) # 树莓派4B增加命令间隔
  166. self.clear()
  167. self.display()
  168. print(" SSD1306硬件初始化成功")
  169. except Exception as e:
  170. print(f" SSD1306硬件初始化失败: {e}")
  171. def _load_fonts(self):
  172. """加载字体"""
  173. self.fonts = {}
  174. if not self.pil_available or not hasattr(self, 'ImageFont'):
  175. print(" 无法加载字体")
  176. return
  177. print(" 加载字体...")
  178. # 中文字体路径(树莓派常用路径)
  179. font_paths = [
  180. '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc',
  181. '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
  182. '/usr/share/fonts/truetype/freefont/FreeSans.ttf'
  183. ]
  184. font_path = None
  185. for path in font_paths:
  186. try:
  187. self.ImageFont.truetype(path, 12)
  188. font_path = path
  189. break
  190. except Exception as e:
  191. print(f" 字体 {path} 不可用: {e}")
  192. if font_path:
  193. print(f" 找到字体: {font_path}")
  194. try:
  195. self.fonts['small'] = self.ImageFont.truetype(font_path, 12)
  196. self.fonts['medium'] = self.ImageFont.truetype(font_path, 14)
  197. self.fonts['large'] = self.ImageFont.truetype(font_path, 18)
  198. print(" 字体加载完成")
  199. except Exception as e:
  200. print(f" 字体加载失败: {e}")
  201. self._load_default_fonts()
  202. else:
  203. print(" 使用默认字体")
  204. self._load_default_fonts()
  205. def _load_default_fonts(self):
  206. """加载默认字体"""
  207. if not self.pil_available or not hasattr(self, 'ImageFont'):
  208. return
  209. try:
  210. self.fonts['small'] = self.ImageFont.load_default(size=10)
  211. self.fonts['medium'] = self.ImageFont.load_default(size=12)
  212. self.fonts['large'] = self.ImageFont.load_default(size=14)
  213. print(" 默认字体加载完成")
  214. except Exception as e:
  215. print(f" 默认字体加载失败: {e}")
  216. def command(self, cmd):
  217. """发送命令"""
  218. if self._device:
  219. try:
  220. self._device.write8(0x00, cmd)
  221. except Exception as e:
  222. print(f" 命令发送失败: {e}")
  223. def clear(self):
  224. """清空显示"""
  225. self.buffer = [0] * (self.width * self.height // 8)
  226. if self.draw:
  227. try:
  228. self.draw.rectangle((0, 0, self.width, self.height), fill=0)
  229. except Exception as e:
  230. print(f" 清屏失败: {e}")
  231. def display(self):
  232. """更新显示"""
  233. if not self._device:
  234. print(" I2C设备未连接")
  235. return
  236. try:
  237. self.command(self.COLUMNADDR)
  238. self.command(0)
  239. self.command(self.width - 1)
  240. self.command(self.PAGEADDR)
  241. self.command(0)
  242. self.command(self.height // 8 - 1)
  243. if self.image:
  244. for y in range(self.height):
  245. for x in range(self.width):
  246. if self.image.getpixel((x, y)):
  247. self.draw_pixel(x, y, True)
  248. for i in range(0, len(self.buffer), 16):
  249. chunk = self.buffer[i:i+16]
  250. self._device.writeList(0x40, chunk)
  251. print(" 显示更新成功")
  252. except Exception as e:
  253. print(f" 显示更新失败: {e}")
  254. def draw_pixel(self, x, y, color=True):
  255. """绘制像素"""
  256. if x < 0 or x >= self.width or y < 0 or y >= self.height:
  257. return
  258. page = y // 8
  259. bit_in_page = y % 8
  260. buffer_index = page * self.width + x
  261. if color:
  262. self.buffer[buffer_index] |= (1 << bit_in_page)
  263. else:
  264. self.buffer[buffer_index] &= ~(1 << bit_in_page)
  265. def draw_text(self, x, y, text, font_size='medium', color=1):
  266. """绘制文本"""
  267. if not self.draw:
  268. print(f" 无法绘制文本: {text}")
  269. return
  270. if not self.fonts or font_size not in self.fonts:
  271. print(f" 使用默认字体绘制: {text}")
  272. if self.pil_available and hasattr(self, 'ImageFont'):
  273. font = self.ImageFont.load_default(size=12)
  274. else:
  275. return
  276. font = self.fonts.get(font_size, self.fonts.get('medium', None))
  277. if not font:
  278. print(f" 无法绘制文本: {text}")
  279. return
  280. try:
  281. color = 1 if color != 0 else 0
  282. self.draw.text((x, y), text, font=font, fill=color)
  283. print(f" 文本绘制成功: '{text}'")
  284. except Exception as e:
  285. print(f" 文本绘制失败 '{text}': {e}")
  286. def draw_text_centered(self, y, text, font_size='medium', color=1):
  287. """居中绘制文本"""
  288. if not self.draw or not self.fonts or font_size not in self.fonts:
  289. self.draw_text(0, y, text, font_size, color)
  290. return
  291. font = self.fonts[font_size]
  292. try:
  293. text_width, text_height = font.getsize(text)
  294. x = (self.width - text_width) // 2
  295. color = 1 if color != 0 else 0
  296. self.draw.text((x, y), text, font=font, fill=color)
  297. print(f" 居中文本绘制成功: '{text}'")
  298. except Exception as e:
  299. print(f" 居中文本绘制失败 '{text}': {e}")
  300. self.draw_text(0, y, text, font_size, color)
  301. def draw_rectangle(self, x, y, width, height, fill=False, outline=True):
  302. """绘制矩形"""
  303. if not self.draw:
  304. print(f" 无法绘制矩形")
  305. return
  306. try:
  307. self.draw.rectangle((x, y, x + width - 1, y + height - 1),
  308. fill=fill, outline=outline)
  309. print(f" 矩形绘制成功")
  310. except Exception as e:
  311. print(f" 矩形绘制失败: {e}")
  312. def get_cpu_temperature():
  313. """获取CPU温度"""
  314. try:
  315. with open('/sys/class/thermal/thermal_zone0/temp', 'r') as f:
  316. temp = float(f.read()) / 1000.0
  317. return temp
  318. except Exception as e:
  319. print(f" 获取温度失败: {e}")
  320. return 0.0
  321. def get_ip_address():
  322. """获取IP地址"""
  323. try:
  324. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  325. s.connect(("8.8.8.8", 80))
  326. ip = s.getsockname()[0]
  327. s.close()
  328. return ip
  329. except Exception as e:
  330. print(f" 获取IP失败: {e}")
  331. return "Unknown"
  332. def format_time_long(seconds):
  333. """格式化时间"""
  334. days = int(seconds // (3600 * 24))
  335. hours = int((seconds % (3600 * 24)) // 3600)
  336. minutes = int((seconds % 3600) // 60)
  337. secs = int(seconds % 60)
  338. if days > 0:
  339. return f"{days}天{hours}时"
  340. elif hours > 0:
  341. return f"{hours}时{minutes}分"
  342. else:
  343. return f"{minutes}分{secs}秒"
  344. def format_bytes_short(bytes_value):
  345. """格式化字节数"""
  346. if bytes_value < 1024:
  347. return f"{bytes_value}B"
  348. elif bytes_value < 1024 * 1024:
  349. return f"{bytes_value / 1024:.0f}K"
  350. elif bytes_value < 1024 * 1024 * 1024:
  351. return f"{bytes_value / (1024 * 1024):.0f}M"
  352. else:
  353. return f"{bytes_value / (1024 * 1024 * 1024):.1f}G"
  354. class ChineseSystemMonitorFinal:
  355. """树莓派4B专用系统监控器"""
  356. def __init__(self):
  357. self.display = None
  358. self.startup_time = time.time()
  359. # 触摸功能配置(树莓派4B优化)
  360. self.GPIO_TOUCH_OUT = 18 # BCM18 (物理引脚12)
  361. self.GPIO_TOUCH_IN = 24 # BCM24 (物理引脚18)
  362. self.last_touch_time = time.time()
  363. self.screen_on = True
  364. self.screen_timeout = 30 # 自动熄屏时间(秒)
  365. self.touch_available = False
  366. print("=" * 60)
  367. print(" 树莓派4B专用OLED监控器 v2.5")
  368. print(" 触摸功能: GPIO18->1M电阻->金属片->GPIO24")
  369. print("⏱ 自动熄屏: 30秒无操作")
  370. print("=" * 60)
  371. print(f"启动时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
  372. print(f"Python版本: {sys.version}")
  373. print("=" * 60)
  374. try:
  375. # 树莓派4B启动延时
  376. print("⏳ 系统启动延迟...")
  377. time.sleep(3)
  378. # 初始化GPIO触摸功能
  379. self.init_gpio()
  380. # 初始化显示
  381. self._init_display()
  382. # 显示启动信息
  383. self._show_startup_screen()
  384. except Exception as e:
  385. print(f" 初始化错误: {e}")
  386. import traceback
  387. traceback.print_exc()
  388. def init_gpio(self):
  389. """初始化树莓派4B GPIO(优化版)"""
  390. print(" 初始化触摸GPIO...")
  391. try:
  392. # 树莓派4B必须用BCM模式
  393. GPIO.setmode(GPIO.BCM)
  394. GPIO.setwarnings(False) # 关闭GPIO警告
  395. # 配置充电GPIO(输出模式)
  396. GPIO.setup(self.GPIO_TOUCH_OUT, GPIO.OUT)
  397. GPIO.output(self.GPIO_TOUCH_OUT, GPIO.LOW) # 初始低电平
  398. # 配置感应GPIO(输入模式)
  399. GPIO.setup(self.GPIO_TOUCH_IN, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
  400. # 验证初始电平(关键调试信息)
  401. print(f" GPIO{self.GPIO_TOUCH_OUT}输出电平: {GPIO.input(self.GPIO_TOUCH_OUT)} (应为0)")
  402. print(f" GPIO{self.GPIO_TOUCH_IN}初始电平: {GPIO.input(self.GPIO_TOUCH_IN)} (未触摸时可能为0或1)")
  403. self.touch_available = True
  404. print(" GPIO初始化成功")
  405. print(f" 物理引脚对应: GPIO18=12号, GPIO24=18号")
  406. except Exception as e:
  407. self.touch_available = False
  408. print(f" GPIO初始化失败: {e}")
  409. print(" 请检查是否安装RPi.GPIO: pip3 install RPi.GPIO")
  410. def check_touch(self):
  411. """树莓派4B专用触摸检测(基于RC电路充放电时间检测)"""
  412. if not self.touch_available:
  413. return False
  414. try:
  415. # 1. 放电阶段:输出低电平,让电容放电
  416. GPIO.output(self.GPIO_TOUCH_OUT, GPIO.LOW)
  417. time.sleep(0.001) # 1ms放电时间
  418. # 2. 充电检测阶段:输出高电平,测量上升沿时间
  419. GPIO.setup(self.GPIO_TOUCH_IN, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
  420. GPIO.output(self.GPIO_TOUCH_OUT, GPIO.HIGH)
  421. # 计算放电时间
  422. discharge_time = 0
  423. start_time = time.time_ns()
  424. threshold = 3000 # 触摸检测阈值,单位微秒
  425. # 等待输入变为高电平或超时
  426. while GPIO.input(self.GPIO_TOUCH_IN) == GPIO.LOW and discharge_time < threshold * 2:
  427. discharge_time = (time.time_ns() - start_time) // 1000 # 转换为微秒
  428. # 3. 恢复初始状态
  429. GPIO.output(self.GPIO_TOUCH_OUT, GPIO.LOW)
  430. # 4. 判断是否触摸
  431. is_touched = discharge_time > threshold
  432. if is_touched:
  433. print(f" 检测到有效触摸! 放电时间: {discharge_time}μs")
  434. return True
  435. else:
  436. # 添加调试信息
  437. print(f" 触摸检测 - 放电时间: {discharge_time}μs (阈值: {threshold}μs)")
  438. except Exception as e:
  439. print(f" 触摸检测异常: {e}")
  440. return False
  441. def turn_screen_on(self):
  442. """树莓派4B屏幕点亮优化(增加时序等待)"""
  443. if not self.display or not hasattr(self.display, 'command'):
  444. return
  445. if not self.screen_on:
  446. print("💡 尝试点亮屏幕...")
  447. # 发送点亮命令
  448. self.display.command(self.display.DISPLAYON)
  449. # 树莓派4B需要等待OLED响应
  450. time.sleep(0.2)
  451. # 强制刷新显示内容
  452. system_info = self.get_system_info()
  453. if system_info:
  454. self.draw_main_screen(system_info)
  455. self.screen_on = True
  456. print(" 屏幕已点亮")
  457. def turn_screen_off(self):
  458. """关闭屏幕"""
  459. if not self.display or not hasattr(self.display, 'command'):
  460. return
  461. if self.screen_on:
  462. self.display.command(self.display.DISPLAYOFF)
  463. self.screen_on = False
  464. print(" 屏幕已关闭")
  465. def _init_display(self):
  466. """初始化显示设备"""
  467. print(" 初始化显示设备...")
  468. # 尝试树莓派常用I2C地址
  469. addresses = [0x3C, 0x3D]
  470. for addr in addresses:
  471. print(f"尝试I2C地址: 0x{addr:02X}")
  472. try:
  473. self.display = SSD1306Final(address=addr)
  474. if self.display:
  475. print(f"✅ 显示设备初始化成功 @ 0x{addr:02X}")
  476. return
  477. except Exception as e:
  478. print(f" 地址0x{addr:02X}初始化失败: {e}")
  479. print(" 创建模拟显示")
  480. self._create_mock_display()
  481. def _create_mock_display(self):
  482. """创建模拟显示对象"""
  483. class MockDisplay:
  484. def __init__(self):
  485. self.draw = None
  486. self.fonts = {}
  487. class MockDraw:
  488. def text(self, *args, **kwargs):
  489. print(f" [模拟] 文本: {args}")
  490. def rectangle(self, *args, **kwargs):
  491. print(f" [模拟] 矩形: {args}")
  492. self.draw = MockDraw()
  493. self.command = lambda x: None
  494. self.display = MockDisplay()
  495. print(" 模拟显示对象创建成功")
  496. def _show_startup_screen(self):
  497. """显示启动屏幕"""
  498. if not self.display or not self.display.draw:
  499. print(" 无法显示启动屏幕")
  500. return
  501. print(" 显示启动屏幕...")
  502. try:
  503. self.display.clear()
  504. self.display.draw_text_centered(10, "系统监控器", 'large')
  505. self.display.draw_text_centered(35, "树莓派4B专用", 'small')
  506. self.display.display()
  507. time.sleep(2)
  508. except Exception as e:
  509. print(f" 启动屏幕显示失败: {e}")
  510. def get_system_info(self):
  511. """获取系统信息"""
  512. try:
  513. cpu_percent = psutil.cpu_percent(interval=0)
  514. memory = psutil.virtual_memory()
  515. disk = psutil.disk_usage('/')
  516. temp = get_cpu_temperature()
  517. system_uptime = time.time() - psutil.boot_time()
  518. monitor_uptime = time.time() - self.startup_time
  519. ip_address = get_ip_address()
  520. return {
  521. 'cpu_percent': cpu_percent,
  522. 'cpu_temp': temp,
  523. 'mem_used': memory.used,
  524. 'mem_total': memory.total,
  525. 'mem_percent': memory.percent,
  526. 'disk_used': disk.used,
  527. 'disk_total': disk.total,
  528. 'disk_percent': disk.percent,
  529. 'system_uptime': system_uptime,
  530. 'monitor_uptime': monitor_uptime,
  531. 'datetime': datetime.now(),
  532. 'ip_address': ip_address
  533. }
  534. except Exception as e:
  535. print(f"⚠️ 获取系统信息失败: {e}")
  536. return {}
  537. def draw_main_screen(self, system_info):
  538. """绘制主屏幕"""
  539. if not self.display or not self.display.draw:
  540. print(" 无法绘制界面")
  541. return False
  542. try:
  543. self.display.clear()
  544. # CPU信息
  545. y_offset = 0
  546. if 'cpu_percent' in system_info and 'cpu_temp' in system_info:
  547. cpu_text = f"CPU: {system_info['cpu_percent']:2.0f}% 温度: {system_info['cpu_temp']:3.0f}°C"
  548. self.display.draw_text(2, y_offset, cpu_text, 'small')
  549. # 内存信息
  550. y_offset += 12
  551. if 'mem_used' in system_info and 'mem_total' in system_info:
  552. ram_used = format_bytes_short(system_info['mem_used'])
  553. ram_total = format_bytes_short(system_info['mem_total'])
  554. ram_text = f"内存: {ram_used}/{ram_total} {system_info['mem_percent']:2.0f}%"
  555. self.display.draw_text(2, y_offset, ram_text, 'small')
  556. # 磁盘信息
  557. y_offset += 14
  558. if 'disk_used' in system_info and 'disk_total' in system_info:
  559. disk_used = format_bytes_short(system_info['disk_used'])
  560. disk_total = format_bytes_short(system_info['disk_total'])
  561. disk_text = f"磁盘: {disk_used}/{disk_total} {system_info['disk_percent']:2.0f}%"
  562. self.display.draw_text(2, y_offset, disk_text, 'small')
  563. # 系统运行时间
  564. y_offset += 14
  565. if 'system_uptime' in system_info:
  566. uptime_str = format_time_long(system_info['system_uptime'])
  567. uptime_text = f"运行: {uptime_str}"
  568. self.display.draw_text(2, y_offset, uptime_text, 'small')
  569. # 底部状态栏
  570. y_offset += 11
  571. if y_offset + 11 <= 64:
  572. if 'datetime' in system_info:
  573. time_str = system_info['datetime'].strftime("%H:%M")
  574. self.display.draw_text(95, y_offset + 1, time_str, 'small', 1)
  575. if 'ip_address' in system_info:
  576. ip_text = f"IP:{system_info['ip_address'][:12]}"
  577. self.display.draw_text(2, y_offset + 1, ip_text, 'small', 1)
  578. self.display.display()
  579. return True
  580. except Exception as e:
  581. print(f" 界面绘制失败: {e}")
  582. return False
  583. def run(self):
  584. """运行监控器"""
  585. print(" 开始监控系统...")
  586. print("按 Ctrl+C 退出")
  587. print(" 触摸金属片点亮屏幕")
  588. print("=" * 60)
  589. try:
  590. update_counter = 0
  591. loop_count = 0
  592. while True:
  593. loop_count += 1
  594. # 1. 检测触摸
  595. touch_detected = self.check_touch()
  596. # 调试用:每隔10次循环显示一次GPIO状态
  597. if self.touch_available and loop_count % 10 == 0:
  598. out_level = GPIO.input(self.GPIO_TOUCH_OUT)
  599. in_level = GPIO.input(self.GPIO_TOUCH_IN)
  600. print(f"[调试] GPIO状态 - OUT({self.GPIO_TOUCH_OUT}): {out_level}, IN({self.GPIO_TOUCH_IN}): {in_level}")
  601. if touch_detected:
  602. print(f" 触摸事件触发 - 时间: {datetime.now().strftime('%H:%M:%S')}")
  603. self.last_touch_time = time.time()
  604. self.turn_screen_on()
  605. # 2. 自动熄屏检查
  606. current_time = time.time()
  607. if self.screen_on and (current_time - self.last_touch_time) > self.screen_timeout:
  608. print(f" 自动熄屏 - 超时时间: {self.screen_timeout}秒")
  609. self.turn_screen_off()
  610. # 3. 屏幕开启时更新显示
  611. if self.screen_on:
  612. system_info = self.get_system_info()
  613. if system_info:
  614. draw_success = self.draw_main_screen(system_info)
  615. update_counter += 1
  616. if update_counter % 5 == 0:
  617. self._print_debug_info(system_info, draw_success)
  618. else:
  619. # 屏幕关闭时也显示简要信息
  620. if loop_count % 50 == 0: # 每50次循环显示一次
  621. print(f"💤 屏幕关闭中... 无操作时间: {current_time - self.last_touch_time:.1f}秒")
  622. # 4. 控制循环频率
  623. time.sleep(0.1) # 提高触摸检测频率(从0.3秒改为0.1秒)
  624. except KeyboardInterrupt:
  625. print("\n 程序被用户中断")
  626. except Exception as e:
  627. print(f"\n 运行时错误: {e}")
  628. import traceback
  629. traceback.print_exc()
  630. finally:
  631. self._cleanup()
  632. def _print_debug_info(self, system_info, draw_success):
  633. """打印调试信息"""
  634. status = "" if draw_success else "❌"
  635. screen_status = "亮" if self.screen_on else "熄"
  636. print(f"[{datetime.now().strftime('%H:%M:%S')}] {status} 屏{screen_status} | "
  637. f"CPU:{system_info.get('cpu_percent', 0):2.0f}%/{system_info.get('cpu_temp', 0):3.0f}°C | "
  638. f"内存:{system_info.get('mem_percent', 0):2.0f}% | "
  639. f"磁盘:{system_info.get('disk_percent', 0):2.0f}%")
  640. def _cleanup(self):
  641. """清理资源"""
  642. print("\n 清理资源...")
  643. # 清理GPIO
  644. try:
  645. if self.touch_available:
  646. GPIO.cleanup([self.GPIO_TOUCH_OUT, self.GPIO_TOUCH_IN])
  647. print(" GPIO资源清理完成")
  648. except Exception as e:
  649. print(f" GPIO清理失败: {e}")
  650. # 清理显示
  651. try:
  652. if self.display and hasattr(self.display, 'clear'):
  653. self.display.clear()
  654. self.display.display()
  655. if hasattr(self.display, 'command'):
  656. self.display.command(self.display.DISPLAYOFF)
  657. print(" 显示清理完成")
  658. except Exception as e:
  659. print(f" 显示清理失败: {e}")
  660. print(" 程序退出")
  661. def main():
  662. """主函数"""
  663. try:
  664. # 检查树莓派4B环境
  665. import platform
  666. print(f"设备信息: {platform.uname()}")
  667. monitor = ChineseSystemMonitorFinal()
  668. monitor.run()
  669. except Exception as e:
  670. print(f"💥 程序启动失败: {e}")
  671. import traceback
  672. traceback.print_exc()
  673. sys.exit(1)
  674. if __name__ == "__main__":
  675. main()