<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>晨曦的宇宙</title>
    <link>https://www.chenxiuniverse.top</link>
    <description>你好，我是晨曦。是一个高中生，没事喜欢用AI让自己的项目落地。</description>
    <language>zh-CN</language>
    <item>
      <title>个人网站迭代记录</title>
      <link>https://www.chenxiuniverse.top/posts/05-personal-site/</link>
      <guid isPermaLink="true">https://www.chenxiuniverse.top/posts/05-personal-site/</guid>
      <pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate>
      <description>从纯 HTML 到 Next.js，迭代了好几版。 第一版：纯 HTML/CSS/JS 最开始用纯 HTML 写的个人主页。星空背景、轮播卡片、天气面板、成就墙、未来方向区... 内容塞太满，文案太精致，AI味很重。 部署在 GitHub …</description>
      <content:encoded>&lt;p&gt;从纯 HTML 到 Next.js，迭代了好几版。&lt;/p&gt;
&lt;h2&gt;第一版：纯 HTML/CSS/JS&lt;/h2&gt;
&lt;p&gt;最开始用纯 HTML 写的个人主页。星空背景、轮播卡片、天气面板、成就墙、未来方向区... 内容塞太满，文案太精致，AI味很重。&lt;/p&gt;
&lt;p&gt;部署在 GitHub Pages + CF Pages 双站，阿里云 DNS 分线路解析。大陆走 GitHub Pages，境外走 CF Pages。&lt;/p&gt;
&lt;p&gt;后来发现 CF Pages 的 Workers 额度被打爆过一次（压测导致的 P0 事故），就彻底解构了 Workers 依赖。&lt;/p&gt;
&lt;h2&gt;第二版：XinghuisamaBlogs&lt;/h2&gt;
&lt;p&gt;在 B 站看到一个毛玻璃风格的博客模板，Next.js 构建，挺好看的。fork 下来改了配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;背景图换成自己的星空图&lt;/li&gt;
&lt;li&gt;简介改成自己的语气&lt;/li&gt;
&lt;li&gt;删掉原作者的示例内容&lt;/li&gt;
&lt;li&gt;添加自己的项目和文章&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;部署在 CF Pages，用 OpenNext 适配器。Windows 上构建有点坑，@cloudflare/next-on-pages 已经废弃，用 @opennextjs/cloudflare 替代。&lt;/p&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;框架：Next.js 16.3.0&lt;/li&gt;
&lt;li&gt;部署：Cloudflare Pages&lt;/li&gt;
&lt;li&gt;域名：chenxiuniverse.top&lt;/li&gt;
&lt;li&gt;备案：萌ICP备 20261024号&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;踩过的坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Next.js 16.2.1 和 OpenNext 适配器版本不兼容，要升级到 16.2.11+&lt;/li&gt;
&lt;li&gt;Google Fonts 在大陆被墙，改成系统字体&lt;/li&gt;
&lt;li&gt;API 路由不能和静态导出共存，改成客户端调用&lt;/li&gt;
&lt;li&gt;wrangler CLI 在 Windows 上有编码问题，用 PowerShell 的 Out-File 指定 UTF-8&lt;/li&gt;
&lt;li&gt;.top 域名不支持 CF Pages 自定义域名绑定&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;目前的功能&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;博客文章（Markdown）&lt;/li&gt;
&lt;li&gt;碎碎念（说说）&lt;/li&gt;
&lt;li&gt;项目展示&lt;/li&gt;
&lt;li&gt;照片墙&lt;/li&gt;
&lt;li&gt;音乐播放&lt;/li&gt;
&lt;li&gt;天气显示&lt;/li&gt;
&lt;li&gt;AI 猫猫（Gemini API）&lt;/li&gt;
&lt;li&gt;暗色/亮色主题切换&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下一步打算把 Gemini API Key 配上，让 AI 猫猫功能上线。&lt;/p&gt;</content:encoded>
    </item>
    <item>
      <title>PiManager：自建 Pillow UI 组件库</title>
      <link>https://www.chenxiuniverse.top/posts/04-pimanager-pillow-ui/</link>
      <guid isPermaLink="true">https://www.chenxiuniverse.top/posts/04-pimanager-pillow-ui/</guid>
      <pubDate>Fri, 19 Jun 2026 00:00:00 GMT</pubDate>
      <description>一个 SSH 管理器，2000 行 UI 重写，13 个自研组件，37 个渲染 bug。MIT 开源。 为什么重写 PiManager 最初用 CustomTkinter 写桌面 SSH 客户端。暗色主题、侧边栏导航、四个功能页（状态监控/…</description>
      <content:encoded>&lt;p&gt;一个 SSH 管理器，2000 行 UI 重写，13 个自研组件，37 个渲染 bug。MIT 开源。&lt;/p&gt;
&lt;h2&gt;为什么重写&lt;/h2&gt;
&lt;p&gt;PiManager 最初用 CustomTkinter 写桌面 SSH 客户端。暗色主题、侧边栏导航、四个功能页（状态监控/文件管理/终端/设置）。用着还行，但有两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CTk 的渲染管线和天气站的 Pillow 图层风格不一致——同一套代码仓里跑两种 UI 范式，格格不入&lt;/li&gt;
&lt;li&gt;CTk 的定制能力有限——想加 hover 渐变、圆角卡片阴影，要么 hack CTk 源码，要么放弃&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;于是决定：把全部 CTk 渲染层换成 Pillow + tkinter Canvas，自建组件库。&lt;/p&gt;
&lt;h2&gt;pillui：13 个组件&lt;/h2&gt;
&lt;p&gt;所有组件继承 BaseComponent，核心方法就四个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;draw(cw, ch, font_scale)：返回 PIL RGBA 图层&lt;/li&gt;
&lt;li&gt;hit_test(mx, my)：鼠标在组件上吗&lt;/li&gt;
&lt;li&gt;on_click()：点击事件回调&lt;/li&gt;
&lt;li&gt;on_drag()：拖拽事件回调&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;渲染管线：每个组件画一层 RGBA → Image.alpha_composite() 叠加 → ImageTk.PhotoImage → 一次性贴到 Canvas。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组件清单：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PillowButton：hover/pressed 三态切换 + 圆角 + 禁用态&lt;/li&gt;
&lt;li&gt;PillowLabel：多行文字 + 左/中/右对齐 + 粗体&lt;/li&gt;
&lt;li&gt;PillowCard：圆角容器 + 标题栏 + 边框&lt;/li&gt;
&lt;li&gt;PillowProgressBar：圆角轨道 + 渐变填充&lt;/li&gt;
&lt;li&gt;PillowSlider：拖拽交互 + 步进值&lt;/li&gt;
&lt;li&gt;PillowCheckBox：方框 + 勾号 + BooleanVar 绑定&lt;/li&gt;
&lt;li&gt;PillowOptionMenu：Pillow 显示面 + 弹出原生 tk.Menu&lt;/li&gt;
&lt;li&gt;PillowScrollFrame：PageCanvas 子类 + 垂直滚动&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;37 个渲染 bug&lt;/h2&gt;
&lt;p&gt;代码写出来了，跑起来才发现 Pillow 渲染暗坑巨多。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;文字周围暗色小框&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;症状：所有按钮上的文字边缘有灰色方框。&lt;/p&gt;
&lt;p&gt;根因：PIL 的 ImageDraw.text() 在透明 RGBA 层上绘字时，抗锯齿的边缘像素与透明黑混合，产生暗色晕轮。&lt;/p&gt;
&lt;p&gt;修复：把文字画在实色背景层上（和组件自身填充色一致），再合成到透明层。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;亮色主题整个页面变黑&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;症状：点了&quot;浅色模式&quot;后所有卡片、标签、按钮全部黑色。&lt;/p&gt;
&lt;p&gt;根因：所有组件在 &lt;strong&gt;init&lt;/strong&gt; 时把颜色写死在实例属性里。refresh_theme() 只改了 Canvas 背景色——组件颜色根本没动。&lt;/p&gt;
&lt;p&gt;修复：给每个组件加 apply_theme() 方法，从 ThemeColors 动态重读颜色令牌。切主题时 PageCanvas 遍历所有组件调 apply_theme()。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;设置页只见最后一张卡片&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;症状：设置页只显示&quot;关于&quot;卡片，前面三张全消失。&lt;/p&gt;
&lt;p&gt;根因：修复&quot;文字小框&quot;时把 create_layer 改成了 create_text_layer。后者创建的是全画布尺寸的不透明层。alpha_composite() 逐层叠加时，每张卡片的全画布不透明层把前面所有内容全部覆盖。&lt;/p&gt;
&lt;p&gt;修复：PillowCard 和 PillowOptionMenu 改回透明层——它们在 draw() 里已经画了自己的圆角矩形填充。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;emoji 全变豆腐块&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;症状：标题里的图标全部渲染为方框。&lt;/p&gt;
&lt;p&gt;根因：Pillow 没有字体回退。msyh.ttc 没有 emoji 字形，PIL 不会自动找 Segoe UI Emoji。&lt;/p&gt;
&lt;p&gt;修复：38 个 emoji 全部替换为纯文字。&lt;/p&gt;
&lt;h2&gt;IP 漂移自动修复&lt;/h2&gt;
&lt;p&gt;树莓派 DHCP 换 IP 后 SSH 连接断开——这是日常问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一版（v2.2.0）：功能写了，不好用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;加了 socket.getaddrinfo() 重解析 + 指数退避重连。但上线后发现四个缺陷：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;主机名被解析后的 IP 覆盖——漂移修复只生效一次&lt;/li&gt;
&lt;li&gt;Keep-alive 从未启动——断线根本检测不到&lt;/li&gt;
&lt;li&gt;重连耗尽后 UI 仍显示&quot;已连接&quot;——用户以为还连着&lt;/li&gt;
&lt;li&gt;Keep-alive 双重触发重连——浪费重试配额&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第二版（v2.2.2）：意外的连接杀手&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;更致命的是：keep-alive 每 30 秒探测一次，单次超时就触发自动重连，而 connect() 第一步是 disconnect()——瞬态超时把健康连接杀死了。&lt;/p&gt;
&lt;p&gt;修复：exec_command 不再触发自动重连；keep-alive 连续 2 次失败才重连。&lt;/p&gt;
&lt;h2&gt;开源&lt;/h2&gt;
&lt;p&gt;2026 年 6 月 19 日，MIT 协议发布：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;13 个 pillui 组件，零外部 UI 依赖&lt;/li&gt;
&lt;li&gt;261 单元测试，全部通过&lt;/li&gt;
&lt;li&gt;5 份文档：README + 架构文档 + API 参考 + 用户指南 + 合规报告&lt;/li&gt;
&lt;li&gt;跨平台：paramiko + Pillow + tkinter，Python 3.9+ 即可运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;技术栈&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;UI 渲染：Pillow + tkinter Canvas（和天气站统一风格）&lt;/li&gt;
&lt;li&gt;组件库：自建 pillui（13 文件）&lt;/li&gt;
&lt;li&gt;SSH：paramiko（Python 标准 SSH 库）&lt;/li&gt;
&lt;li&gt;主题：ThemeColors 类（暗/亮双模式 + 三套强调色）&lt;/li&gt;
&lt;li&gt;测试：unittest（标准库，零依赖）&lt;/li&gt;
&lt;/ul&gt;</content:encoded>
    </item>
    <item>
      <title>智能天气钟表</title>
      <link>https://www.chenxiuniverse.top/posts/02-weather-clock/</link>
      <guid isPermaLink="true">https://www.chenxiuniverse.top/posts/02-weather-clock/</guid>
      <pubDate>Tue, 09 Sep 2025 00:00:00 GMT</pubDate>
      <description>ESP32 + SSD1306 OLED + MAX30102 血氧传感器。能看天气、测血氧、显示时间。区二等奖。 硬件 ESP32 开发板（CP210 系列驱动） SSD1306 OLED 屏幕（128x64，SPI 接口） MAX301…</description>
      <content:encoded>&lt;p&gt;ESP32 + SSD1306 OLED + MAX30102 血氧传感器。能看天气、测血氧、显示时间。区二等奖。&lt;/p&gt;
&lt;h2&gt;硬件&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ESP32 开发板（CP210 系列驱动）&lt;/li&gt;
&lt;li&gt;SSD1306 OLED 屏幕（128x64，SPI 接口）&lt;/li&gt;
&lt;li&gt;MAX30102 血氧传感器（I2C 接口）&lt;/li&gt;
&lt;li&gt;两节 21700 电池并联供电（5V 输出）&lt;/li&gt;
&lt;li&gt;三个按键（GPIO 下拉电阻）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;功能&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;天气显示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;调用 OpenWeather API，HTTP 协议（ESP32 固件不支持 HTTPS）。urequests 库发请求，ujson 解析返回数据。OpenWeather 免费版一天可以调用 1000 次，够用。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网络校时&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ntptime 同步 UTC 时间，加 8 小时转东八区。用阿里、微软等大厂的 NTP 服务器，保证稳定。每隔固定循环次数重新校时。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;血氧检测&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MAX30102 采集红光和红外光，I2C 协议通讯。巴特沃斯带通滤波 + 相关系数算法计算血氧饱和度。&lt;/p&gt;
&lt;p&gt;公式：R = (red_ac/red_dc) / (ir_ac/ir_dc)，SPO2 = a - b*R。系数 a=106, b=17 需要根据实际情况校准。&lt;/p&gt;
&lt;p&gt;前 45 秒数据不准，要等稳定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;中文显示&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;自己写了 16x16 点阵字库。把汉字拆成单个字，通过字典找字模数据，创建帧缓冲区，最后贴到屏幕上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;按键切换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4 个界面，GPIO 下拉电阻检测。用 machine.Pin 的 pull 参数实现。&lt;/p&gt;
&lt;h2&gt;踩过的坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MicroPython 不原生支持 SSD1306，要用第三方库&lt;/li&gt;
&lt;li&gt;固件不支持 HTTPS，只能用 HTTP 的 API&lt;/li&gt;
&lt;li&gt;血氧数据前 45 秒不准，要等稳定&lt;/li&gt;
&lt;li&gt;NTP 服务器有时候连不上，加了多个备选&lt;/li&gt;
&lt;li&gt;字库文件要单独存放，方便扩展&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;p&gt;MicroPython 写的，ESP32 上直接跑。字库是单独的 .py 文件，导入后使用。&lt;/p&gt;</content:encoded>
    </item>
    <item>
      <title>树莓派智能驾驶小车</title>
      <link>https://www.chenxiuniverse.top/posts/03-smart-car/</link>
      <guid isPermaLink="true">https://www.chenxiuniverse.top/posts/03-smart-car/</guid>
      <pubDate>Mon, 09 Jun 2025 00:00:00 GMT</pubDate>
      <description>Raspberry Pi Pico + L239D 电机驱动 + 双 18650 电池。四驱，坦克转向。区二等奖。 硬件清单 Raspberry Pi Pico（焊好针脚） L239D 电源控制芯片 四个直流电机（四驱底座） 两节 1865…</description>
      <content:encoded>&lt;p&gt;Raspberry Pi Pico + L239D 电机驱动 + 双 18650 电池。四驱，坦克转向。区二等奖。&lt;/p&gt;
&lt;h2&gt;硬件清单&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi Pico（焊好针脚）&lt;/li&gt;
&lt;li&gt;L239D 电源控制芯片&lt;/li&gt;
&lt;li&gt;四个直流电机（四驱底座）&lt;/li&gt;
&lt;li&gt;两节 18650 串联电池盒（7.2V，给电机供电）&lt;/li&gt;
&lt;li&gt;单节 18650 电池盒 + 5V 升压稳压模块（给树莓派供电）&lt;/li&gt;
&lt;li&gt;面包板 + 杜邦线若干&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;原理&lt;/h2&gt;
&lt;p&gt;PWM 控制电源芯片，电源芯片驱动直流电机。坦克转向——不使用舵机，通过控制左右轮转速差实现转向。&lt;/p&gt;
&lt;p&gt;L239D 有两个供电接口：Vcc1 为芯片供电 5V，Vcc2 为电机供电。我用了两节 18650 串联的 7.2V 电源。L239D 标称可承受最高 36V，够用。&lt;/p&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;p&gt;MicroPython 写的。核心是 machine 库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;电机控制：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;machine.Pin(16, machine.Pin.OUT) 控制电机正反转&lt;/li&gt;
&lt;li&gt;machine.PWM(machine.Pin(21)) 控制转速&lt;/li&gt;
&lt;li&gt;duty_u16() 设占空比，公式：speed/100*65535&lt;/li&gt;
&lt;li&gt;freq(50) 固定频率，直流电机不需要变频&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;接线：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;左右轮各一组引脚：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GP16/GP17 控制正反转&lt;/li&gt;
&lt;li&gt;GP21 控制速度（PWM）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;调好一边，再写另一边。切换方向时先把所有针脚 value(0)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;运行时间：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;utime.sleep(3) 控制运行 3 秒。&lt;/p&gt;
&lt;h2&gt;踩过的坑&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;四驱底座的电机是对着的，接线前要用电池逐个测试转向，反转就把正负极对调&lt;/li&gt;
&lt;li&gt;面包板建议买专业的，电机线接窄板&lt;/li&gt;
&lt;li&gt;通电后摸电池正负线，发热立即断电&lt;/li&gt;
&lt;li&gt;Thonny 往树莓派写文件，PyCharm 写代码（有辅助和 debug）&lt;/li&gt;
&lt;li&gt;保存到树莓派的文件名必须是 main.py，通电自动运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;部署&lt;/h2&gt;
&lt;p&gt;写完代码后，在 Thonny 里点保存，选 Raspberry Pico，文件名改成 main。通电后自动运行。&lt;/p&gt;
&lt;p&gt;改代码时要先把所有针脚停止，否则轮子会一直转。&lt;/p&gt;</content:encoded>
    </item>
  </channel>
</rss>