Huaiyao Jin

Huaiyao Jin

把运动统计写在 iPhone 锁屏上,还会自动切换

先看下效果。

之前总觉得 iPhone 锁屏界面中间这部分区域不能自定义一些文字有点遗憾。倘若能放一些重要的提醒或者运动统计信息,可以更好地提醒自己、督促自己运动。毕竟手机一点亮就可以看到。

之前也问过 AI,都没有得到什么好的方案。

直到有一天,同事发给我这个网站。

https://lifegrid-wallpapers.pages.dev/

受它的启发,一通研究,最后实现我想要的效果。真是念念不忘,必有回响。

大致流程如下。

下面是实现过程。

Notion 里记录运动

快捷输入,参照 Notion+Scriptable+快捷指令,每天完成最重要的事

Mac mini 定期获取运动历史记录,并上传到云服务器

参照一台 Mac mini 的日常用途分享

55 23 * * * /bin/bash /Users/jinhuaiyao/Nextcloud/Config/Mac_Script/upload_heatmap_data.sh >/Users/jinhuaiyao/Log/upload_heatmap_data.txt 2>&1
(调用 notion api 获取特定的数据,上传到云服务器)

在原有的 job上 添加三段。

jinhuaiyao@huaiyaos-mac-mini ~ % cat /Users/jinhuaiyao/Nextcloud/Config/Mac_Script/upload_heatmap_data.sh
cd /Users/jinhuaiyao/.tmp/

DT=`date +%Y_%m_%d`

for file in `ls *.txt`
do
cp $file /Users/jinhuaiyao/Nextcloud/Backup/backup_heatmap/${file}.${DT}
done

bash workout.sh
bash running.sh
bash reading.sh
bash vocabulary.sh
bash englishpod.sh
bash plank.sh

cat workout_2026.txt | awk -F'跑步|公里' '/跑步/ {sum += $2; count++} END {printf "跑步总次数: %d 次\n跑步总长度: %.1f 公里\n", count, sum}' >workout_2026_sum.txt

cat workout_2026.txt | awk -F'跳绳|个' '/跳绳/ {sum += $2; count++} END {printf "跳绳总次数: %d 次\n跳绳总个数: %.f 个\n", count, sum}' >>workout_2026_sum.txt

awk -F'|' '{
    count++;
    t = $3; gsub(/ /, "", t);
    m = 0; s = 0;
    if (t ~ /分/ && t ~ /秒/) {
        split(t, a, "分"); m = a[1]; sub(/秒/, "", a[2]); s = a[2];
    } else if (t ~ /分钟/) {
        sub(/分钟/, "", t); m = t;
    } else if (t ~ /分/) {
        sub(/分/, "", t); m = t;
    }
    total = m * 60 + s;
    if (total > max) { max = total; max_str = $3 }
}
END {
    printf "平板支撑总次数: %d 次\n平板支撑最长时间: %s\n", count, max_str
}' plank_2026.txt >>workout_2026_sum.txt

scp -P 10086 *.txt [email protected]:/home/xx/www/webpage

cd  /Users/jinhuaiyao/Nextcloud/Backup/backup_heatmap/
find . -type f -mtime +20 -exec rm {} \;

三个 txt 文件的格式如下:

jinhuaiyao@huaiyaos-mac-mini .tmp % tail -5 workout_2026.txt
2026-02-12 |1 | 跳绳800个
2026-02-13 |1 | 跳绳800个
2026-02-14 |1 | 跳绳400个
2026-02-20 |1 | 跳绳1200个
2026-02-21 |1 | 跳绳1500个

jinhuaiyao@huaiyaos-mac-mini .tmp % tail -5 plank_2026.txt
2026-02-10 |1 | 1分45秒
2026-02-12 |1 | 1分30秒
2026-02-19 |1 | 1分45秒
2026-02-20 |1 | 1分15秒
2026-02-21 |1 | 3分钟

jinhuaiyao@huaiyaos-mac-mini .tmp % tail workout_2026_sum.txt
跑步总次数: 9 次
跑步总长度: 58.3 公里
跳绳总次数: 12 次
跳绳总个数: 13000 个
平板支撑总次数: 27 次
平板支撑最长时间:  3分钟

云服务器定期运行脚本生成带有统计信息的壁纸

# cron job
25 * * * * /root/wallpaper_env/bin/python3 /root/wallpaper_env/generate_wallpaper.py >/root/generate_wallpaper.log  2>&1

root@meta-unicorn-2:~# cat /root/wallpaper_env/generate_wallpaper.py
import requests
from PIL import Image, ImageDraw, ImageFont
import re
import os

# --- 配置区 ---
DATA_URL = "https://xx.xx.com/workout_2026_sum.txt"
BASE_IMAGE_PATH = "/root/wallpaper_env/base_wallpaper.jpg"
OUTPUT_PATH = "/home/xx/www/webpage/final_wallpaper.png"
WIDTH, HEIGHT = 1260, 2736

# 字体路径
FONT_PATH = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
if not os.path.exists(FONT_PATH):
    FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"

def get_workout_data():
    try:
        r = requests.get(DATA_URL, timeout=10)
        r.encoding = 'utf-8'
        text = r.text
        return {
            "dist": re.search(r"跑步总长度:\s*([\d.]+)", text).group(1),
            "run_count": re.search(r"跑步总次数:\s*(\d+)", text).group(1),
            "rope_count": re.findall(r"跳绳总次数:\s*(\d+)", text)[0],
            "rope_num": re.search(r"跳绳总个数:\s*(\d+)", text).group(1),
            "plank_count": re.search(r"平板支撑总次数:\s*(\d+)", text).group(1),
            "plank_time": re.search(r"平板支撑最长时间:\s*(.+)", text).group(1).strip()
        }
    except Exception as e:
        print(f"Data parse error: {e}")
        return None

def generate_wallpaper():
    data = get_workout_data()
    if not data: return

    try:
        base_img = Image.open(BASE_IMAGE_PATH).convert('RGB')
        img = base_img.resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
    except Exception as e:
        print(f"Image error: {e}")
        return

    draw = ImageDraw.Draw(img)

    # 1. 字体与颜色
    f_xl = ImageFont.truetype(FONT_PATH, 100)
    f_md = ImageFont.truetype(FONT_PATH, 40)
    f_sm = ImageFont.truetype(FONT_PATH, 40)
    C_BLUE = "#68abe7"
    C_WHITE = "#FFFFFF"

    # 2. 布局基准
    X_POS = WIDTH * 0.12
    Y_START = HEIGHT * 0.50
    SECTION_GAP = 270

    def draw_data_block(start_y, label, val, unit):
        draw.text((X_POS, start_y), label, fill=C_BLUE, font=f_sm)
        draw.text((X_POS, start_y + 60), val, fill=C_WHITE, font=f_xl)
        val_w = f_xl.getlength(val)
        draw.text((X_POS + val_w + 20, start_y + 125), unit, fill=C_BLUE, font=f_md)

    def draw_separator(y_pos):
        draw.line([(X_POS, y_pos), (X_POS + 200, y_pos)], fill=C_BLUE, width=3)

    # --- 开始绘制 ---
    curr_y = Y_START

    # 1. 跑步
    draw_data_block(curr_y, f"跑步 - {data['run_count']} 次", data['dist'], "公里")
    draw_separator(curr_y + 240)

    # 2. 跳绳
    curr_y += SECTION_GAP
    draw_data_block(curr_y, f"跳绳 - {data['rope_count']} 次", data['rope_num'], "个")
    draw_separator(curr_y + 240)

    # 3. 平板支撑
    curr_y += SECTION_GAP
    draw_data_block(curr_y, f"平板支撑 - {data['plank_count']} 次", data['plank_time'], "最长")

    # 垂直线
    draw.line([(X_POS - 45, Y_START), (X_POS - 45, curr_y + 190)], fill=C_BLUE, width=7)

    img.save(OUTPUT_PATH, "PNG")
    print(f"Successfully generated: {OUTPUT_PATH}")

if __name__ == "__main__":
    generate_wallpaper()

大致过程是:

先在网上下载一个适合手机分辨率的原有壁纸(每个机型会不一样),放到

/root/wallpaper_env/base_wallpaper.jpg

Python 脚本读取

/home/xx/www/webpage/workout_2026_sum.txt

的内容,将内容绘制到 base_wallpaper.jpg 上,生成带有文字的新的壁纸。

/home/xx/www/webpage/final_wallpaper.png

这个文件可以通过以下 URL 访问到。

https://xx.xx.com/final_wallpaper.png

iPhone快捷指令每天定时更新壁纸

设计一个快捷指令,并设置自动化每天凌晨运行一次。

完工!