<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>U1 on 腾图工作室,威远博客,威远工作室,Ease</title>
    <link>/tags/u1/</link>
    <description>Recent content in U1 on 腾图工作室,威远博客,威远工作室,Ease</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Sat, 13 Jun 2026 09:42:48 +0000</lastBuildDate>
    <atom:link href="/tags/u1/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>关于Snapmaker U1的扩展固件</title>
      <link>/it/2026/061317-u1ext/</link>
      <pubDate>Sat, 13 Jun 2026 09:42:48 +0000</pubDate>
      <guid>/it/2026/061317-u1ext/</guid>
      <description>&lt;p&gt;购买U1的一个理由是它基于Klipper 定制固件，希望有更多网友开发出更强大的功能。于是终于还是刷了扩展固件。&lt;/p&gt;&#xA;&lt;p&gt;从使用来看，功能确实丰富了比较多，更新也比较及时(紧跟官方版本升级)&lt;br&gt;&#xA;感受明显的是摄像头，流畅，象换了一个人。&lt;/p&gt;</description>
    </item>
    <item>
      <title>与U1打印机(Klipper)通信，获取打印数据</title>
      <link>/code/2026/031515-u1/</link>
      <pubDate>Sun, 15 Mar 2026 07:52:31 +0000</pubDate>
      <guid>/code/2026/031515-u1/</guid>
      <description>&lt;p&gt;以下代码在命令行获取Snapmaker U1打印机(Klipper系统)的实时打印信息&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../031515-u1-01.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;#&#xA;# 实时显示U1打印机信息&#xA;# 2026.3.15&#xA;# https://i.scwy.net&#xA;# Ease&#xA;#&#xA;&#xA;import asyncio&#xA;import websockets&#xA;import json&#xA;import aiohttp&#xA;from datetime import datetime, timedelta&#xA;import sys&#xA;import os&#xA;import subprocess&#xA;&#xA;# ===================== 打印机配置 =====================&#xA;MOONRAKER_IP = &amp;#34;192.168.1.81&amp;#34;&#xA;MOONRAKER_PORT = 7125&#xA;MOONRAKER_WS_URL = f&amp;#34;ws://{MOONRAKER_IP}:{MOONRAKER_PORT}/websocket&amp;#34;&#xA;QUERY_INTERVAL = 5  # 基础刷新间隔（秒）&#xA;MATERIAL_REFRESH_INTERVAL = 12  # 材料数据刷新间隔（单位：基础刷新次数）&#xA;TIMEOUT = 10&#xA;# 支持4个挤出机配置&#xA;EXTRUDERS = [&amp;#34;extruder&amp;#34;, &amp;#34;extruder1&amp;#34;, &amp;#34;extruder2&amp;#34;, &amp;#34;extruder3&amp;#34;]&#xA;# ======================================================&#xA;&#xA;# ========== 美化相关配置 ==========&#xA;class Colors:&#xA;    &amp;#34;&amp;#34;&amp;#34;终端颜色配置（仅Windows PowerShell/CMD支持）&amp;#34;&amp;#34;&amp;#34;&#xA;    RESET = &amp;#34;\033[0m&amp;#34;&#xA;    RED = &amp;#34;\033[31m&amp;#34;&#xA;    GREEN = &amp;#34;\033[32m&amp;#34;&#xA;    YELLOW = &amp;#34;\033[33m&amp;#34;&#xA;    BLUE = &amp;#34;\033[34m&amp;#34;&#xA;    PURPLE = &amp;#34;\033[35m&amp;#34;&#xA;    CYAN = &amp;#34;\033[36m&amp;#34;&#xA;    WHITE = &amp;#34;\033[37m&amp;#34;&#xA;    BOLD = &amp;#34;\033[1m&amp;#34;&#xA;    UNDERLINE = &amp;#34;\033[4m&amp;#34;&#xA;&#xA;# 兼容Windows终端颜色&#xA;if sys.platform.startswith(&amp;#39;win&amp;#39;):&#xA;    # 启用Windows终端ANSI颜色支持&#xA;    os.system(&amp;#39;&amp;#39;)&#xA;&#xA;def init_console():&#xA;    &amp;#34;&amp;#34;&amp;#34;初始化控制台（新增PowerShell标题初始化）&amp;#34;&amp;#34;&amp;#34;&#xA;    if sys.platform.startswith(&amp;#39;win&amp;#39;):&#xA;        import io&#xA;        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding=&amp;#39;utf-8&amp;#39;, line_buffering=True)&#xA;        os.system(&amp;#39;cls&amp;#39;)&#xA;        # 初始化PowerShell标题&#xA;        set_powershell_title(&amp;#34;3D打印机监控 - 初始化中...&amp;#34;)&#xA;    else:&#xA;        sys.stdout.reconfigure(line_buffering=True) if hasattr(sys.stdout, &amp;#39;reconfigure&amp;#39;) else None&#xA;        os.system(&amp;#39;clear&amp;#39;)&#xA;&#xA;def set_powershell_title(title):&#xA;    &amp;#34;&amp;#34;&amp;#34;&#xA;    设置PowerShell窗口标题（仅Windows生效）&#xA;    :param title: 标题文本&#xA;    &amp;#34;&amp;#34;&amp;#34;&#xA;    if not sys.platform.startswith(&amp;#39;win&amp;#39;):&#xA;        return&#xA;    &#xA;    # 限制标题最大长度（PowerShell建议不超过80字符）&#xA;    MAX_TITLE_LENGTH = 80&#xA;    if len(title) &amp;gt; MAX_TITLE_LENGTH:&#xA;        title = title[:MAX_TITLE_LENGTH - 3] + &amp;#34;...&amp;#34;&#xA;    &#xA;    try:&#xA;        # 方法1：通过PowerShell命令设置标题（使用转义处理）&#xA;        # 替换单引号为双引号避免语法错误&#xA;        escaped_title = title.replace(&amp;#34;&amp;#39;&amp;#34;, &amp;#34;\&amp;#34;&amp;#34;)&#xA;        subprocess.run(&#xA;            [&amp;#34;powershell&amp;#34;, &amp;#34;-Command&amp;#34;, f&amp;#34;$Host.UI.RawUI.WindowTitle = &amp;#39;{escaped_title}&amp;#39;&amp;#34;],&#xA;            capture_output=True,&#xA;            encoding=&amp;#39;utf-8&amp;#39;,&#xA;            timeout=1&#xA;        )&#xA;    except Exception:&#xA;        try:&#xA;            # 备用方法：通过cmd title命令（更稳定）&#xA;            # cmd title命令不支持特殊字符，简化处理&#xA;            simple_title = title.replace(&amp;#34;|&amp;#34;, &amp;#34;-&amp;#34;).replace(&amp;#34;:&amp;#34;, &amp;#34;=&amp;#34;)&#xA;            os.system(f&amp;#34;title {simple_title}&amp;#34;)&#xA;        except:&#xA;            pass&#xA;&#xA;def safe_get(data, keys, default=None):&#xA;    &amp;#34;&amp;#34;&amp;#34;安全获取嵌套字典值&amp;#34;&amp;#34;&amp;#34;&#xA;    if not isinstance(data, dict):&#xA;        return default&#xA;    current = data&#xA;    for key in keys:&#xA;        if isinstance(current, dict) and key in current:&#xA;            current = current[key]&#xA;        else:&#xA;            return default&#xA;    return current if current is not None else default&#xA;&#xA;def safe_float(value, default=0.0):&#xA;    &amp;#34;&amp;#34;&amp;#34;安全转换为浮点数&amp;#34;&amp;#34;&amp;#34;&#xA;    try:&#xA;        return float(value) if value is not None else default&#xA;    except:&#xA;        return default&#xA;&#xA;def safe_int(value, default=0):&#xA;    &amp;#34;&amp;#34;&amp;#34;安全转换为整数&amp;#34;&amp;#34;&amp;#34;&#xA;    try:&#xA;        return int(value) if value is not None and value != &amp;#34;&amp;#34; else default&#xA;    except:&#xA;        return default&#xA;&#xA;def format_seconds_short(seconds):&#xA;    &amp;#34;&amp;#34;&amp;#34;精简版时间格式化（适配标题显示：7h19m）&amp;#34;&amp;#34;&amp;#34;&#xA;    sec = safe_float(seconds)&#xA;    if sec &amp;lt;= 0:&#xA;        return &amp;#34;未知&amp;#34;&#xA;    hours = int(sec // 3600)&#xA;    mins = int((sec % 3600) // 60)&#xA;    parts = []&#xA;    if hours &amp;gt; 0:&#xA;        parts.append(f&amp;#34;{hours}h&amp;#34;)&#xA;    parts.append(f&amp;#34;{mins}m&amp;#34;)&#xA;    return &amp;#34;&amp;#34;.join(parts)&#xA;&#xA;def format_seconds(seconds):&#xA;    &amp;#34;&amp;#34;&amp;#34;完整时间格式化（界面显示：7h 19m 47s）&amp;#34;&amp;#34;&amp;#34;&#xA;    sec = safe_float(seconds)&#xA;    if sec &amp;lt;= 0:&#xA;        return &amp;#34;未知&amp;#34;&#xA;    hours = int(sec // 3600)&#xA;    mins = int((sec % 3600) // 60)&#xA;    secs = int(sec % 60)&#xA;    parts = []&#xA;    if hours &amp;gt; 0:&#xA;        parts.append(f&amp;#34;{hours}h&amp;#34;)&#xA;    if mins &amp;gt; 0 or hours &amp;gt; 0:  # 有小时就显示分钟&#xA;        parts.append(f&amp;#34;{mins}m&amp;#34;)&#xA;    parts.append(f&amp;#34;{secs}s&amp;#34;)&#xA;    return &amp;#34; &amp;#34;.join(parts)&#xA;&#xA;def calculate_remaining_time(print_duration, progress):&#xA;    &amp;#34;&amp;#34;&amp;#34;&#xA;    计算剩余时间&#xA;    :param print_duration: 已打印时间（秒）&#xA;    :param progress: 打印进度（百分比）&#xA;    :return: 剩余时间（秒）&#xA;    &amp;#34;&amp;#34;&amp;#34;&#xA;    if progress &amp;lt;= 0 or print_duration &amp;lt;= 0:&#xA;        return 0&#xA;    &#xA;    # 总预计时间 = 已打印时间 / 进度&#xA;    total_estimated = print_duration / (progress / 100)&#xA;    # 剩余时间 = 总预计时间 - 已打印时间&#xA;    remaining = total_estimated - print_duration&#xA;    &#xA;    return max(0, remaining)&#xA;&#xA;def get_status_color(print_state):&#xA;    &amp;#34;&amp;#34;&amp;#34;根据打印状态返回对应颜色&amp;#34;&amp;#34;&amp;#34;&#xA;    state_map = {&#xA;        &amp;#34;printing&amp;#34;: Colors.GREEN,      # 打印中 - 绿色&#xA;        &amp;#34;paused&amp;#34;: Colors.YELLOW,       # 暂停 - 黄色&#xA;        &amp;#34;standby&amp;#34;: Colors.BLUE,        # 待机 - 蓝色&#xA;        &amp;#34;error&amp;#34;: Colors.RED,           # 错误 - 红色&#xA;        &amp;#34;complete&amp;#34;: Colors.PURPLE      # 完成 - 紫色&#xA;    }&#xA;    return state_map.get(print_state.lower(), Colors.WHITE)&#xA;&#xA;# ========== 料盘数据获取函数（独立封装） ==========&#xA;async def get_filament_info():&#xA;    &amp;#34;&amp;#34;&amp;#34;获取料盘（材料）数据&amp;#34;&amp;#34;&amp;#34;&#xA;    try:&#xA;        async with websockets.connect(MOONRAKER_WS_URL, ping_interval=5) as ws:&#xA;            # 查询所有关键数据（你的原始请求）&#xA;            req = json.dumps({&#xA;                &amp;#34;jsonrpc&amp;#34;: &amp;#34;2.0&amp;#34;,&#xA;                &amp;#34;method&amp;#34;: &amp;#34;printer.objects.query&amp;#34;,&#xA;                &amp;#34;params&amp;#34;: {&#xA;                    &amp;#34;objects&amp;#34;: {&#xA;                        &amp;#34;print_task_config&amp;#34;: None,  # 颜色数据所在&#xA;                    }&#xA;                },&#xA;                &amp;#34;id&amp;#34;: 1&#xA;            })&#xA;            await ws.send(req)&#xA;&#xA;            while True:&#xA;                msg = await ws.recv()&#xA;                data = json.loads(msg)&#xA;&#xA;                if data.get(&amp;#34;id&amp;#34;) == 1 and &amp;#34;result&amp;#34; in data:&#xA;                    status = data[&amp;#34;result&amp;#34;][&amp;#34;status&amp;#34;]&#xA;                    &#xA;                    # 读取 print_task_config 里的颜色数据（你的代码）&#xA;                    task_config = status.get(&amp;#34;print_task_config&amp;#34;, {})&#xA;                    rgba_list = task_config.get(&amp;#34;filament_color_rgba&amp;#34;, [&amp;#34;未知&amp;#34;]*4)&#xA;                    filament_type = task_config.get(&amp;#34;filament_type&amp;#34;, [&amp;#34;未知&amp;#34;]*4)&#xA;                    &#xA;                    # 转换为中文颜色名称（你的代码）&#xA;                    def rgba_hex_to_color_name(rgba_hex):&#xA;                        &amp;#34;&amp;#34;&amp;#34;把RGBA十六进制（如FFFFFFFF）转成中文颜色名称&amp;#34;&amp;#34;&amp;#34;&#xA;                        # 只取前6位RGB（忽略最后2位A）&#xA;                        rgb_hex = rgba_hex[:6].upper() if rgba_hex != &amp;#34;未知&amp;#34; else &amp;#34;未知&amp;#34;&#xA;                        &#xA;                        # 常见颜色映射（可根据你的实际颜色扩展）&#xA;                        color_map = {&#xA;                            &amp;#34;FFFFFF&amp;#34;: &amp;#34;白色&amp;#34;,&#xA;                            &amp;#34;000000&amp;#34;: &amp;#34;黑色&amp;#34;,&#xA;                            &amp;#34;FF0000&amp;#34;: &amp;#34;红色&amp;#34;,&#xA;                            &amp;#34;00FF00&amp;#34;: &amp;#34;绿色&amp;#34;,&#xA;                            &amp;#34;0000FF&amp;#34;: &amp;#34;蓝色&amp;#34;,&#xA;                            &amp;#34;FFFF00&amp;#34;: &amp;#34;黄色&amp;#34;,&#xA;                            &amp;#34;FF00FF&amp;#34;: &amp;#34;紫色&amp;#34;,&#xA;                            &amp;#34;00FFFF&amp;#34;: &amp;#34;青色&amp;#34;,&#xA;                            &amp;#34;DE1619&amp;#34;: &amp;#34;红色&amp;#34;,&#xA;                            &amp;#34;808080&amp;#34;: &amp;#34;灰色&amp;#34;,&#xA;                            &amp;#34;FFA500&amp;#34;: &amp;#34;橙色&amp;#34;&#xA;                        }&#xA;                        &#xA;                        # 匹配预设颜色，匹配不到则显示原始值&#xA;                        return color_map.get(rgb_hex, f&amp;#34;#{rgb_hex}&amp;#34;)&#xA;                    &#xA;                    colors = [rgba_hex_to_color_name(rgba) for rgba in rgba_list]&#xA;                    &#xA;                    # 整理4卷耗材信息&#xA;                    multi_filament = []&#xA;                    for i in range(4):&#xA;                        multi_filament.append({&#xA;                            &amp;#34;index&amp;#34;: i+1,&#xA;                            &amp;#34;type&amp;#34;: filament_type[i] if i &amp;lt; len(filament_type) else &amp;#34;未配置&amp;#34;,&#xA;                            &amp;#34;color&amp;#34;: colors[i] if i &amp;lt; len(colors) else &amp;#34;未配置&amp;#34;,&#xA;                            &amp;#34;diameter&amp;#34;: &amp;#34;1.75mm&amp;#34;&#xA;                        })&#xA;                    &#xA;                    return {&#xA;                        &amp;#34;multi_filament&amp;#34;: multi_filament,&#xA;                        &amp;#34;filament_type&amp;#34;: filament_type,&#xA;                        &amp;#34;colors&amp;#34;: colors&#xA;                    }&#xA;                    &#xA;    except Exception as e:&#xA;        print(f&amp;#34;{Colors.RED}⚠️ 获取料盘数据异常: {type(e).__name__} - {e}{Colors.RESET}&amp;#34;)&#xA;        # 返回默认值&#xA;        return {&#xA;            &amp;#34;multi_filament&amp;#34;: [&#xA;                {&amp;#34;index&amp;#34;:1, &amp;#34;type&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;color&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;diameter&amp;#34;:&amp;#34;1.75mm&amp;#34;},&#xA;                {&amp;#34;index&amp;#34;:2, &amp;#34;type&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;color&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;diameter&amp;#34;:&amp;#34;1.75mm&amp;#34;},&#xA;                {&amp;#34;index&amp;#34;:3, &amp;#34;type&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;color&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;diameter&amp;#34;:&amp;#34;1.75mm&amp;#34;},&#xA;                {&amp;#34;index&amp;#34;:4, &amp;#34;type&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;color&amp;#34;:&amp;#34;未配置&amp;#34;, &amp;#34;diameter&amp;#34;:&amp;#34;1.75mm&amp;#34;}&#xA;            ],&#xA;            &amp;#34;filament_type&amp;#34;: [&amp;#34;未配置&amp;#34;]*4,&#xA;            &amp;#34;colors&amp;#34;: [&amp;#34;未配置&amp;#34;]*4&#xA;        }&#xA;&#xA;async def get_file_metadata(filename):&#xA;    &amp;#34;&amp;#34;&amp;#34;获取打印文件的详细元数据（包含切片预估时间）&amp;#34;&amp;#34;&amp;#34;&#xA;    if not filename or filename == &amp;#34;无&amp;#34;:&#xA;        return {}&#xA;    &#xA;    try:&#xA;        async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=TIMEOUT)) as session:&#xA;            # 获取文件详细信息（包含切片时的预估时间）&#xA;            url = f&amp;#34;http://{MOONRAKER_IP}:{MOONRAKER_PORT}/server/files/metadata?filename={filename}&amp;#34;&#xA;            async with session.get(url) as resp:&#xA;                if resp.status == 200:&#xA;                    return await resp.json()&#xA;                else:&#xA;                    print(f&amp;#34;{Colors.YELLOW}⚠️ 获取文件元数据失败，状态码: {resp.status}{Colors.RESET}&amp;#34;)&#xA;                    return {}&#xA;    except Exception as e:&#xA;        print(f&amp;#34;{Colors.RED}⚠️ 获取文件元数据异常: {str(e)}{Colors.RESET}&amp;#34;)&#xA;        return {}&#xA;&#xA;async def get_full_printer_data():&#xA;    &amp;#34;&amp;#34;&amp;#34;获取完整的打印机数据（新增print_stats.estimated_time字段）&amp;#34;&amp;#34;&amp;#34;&#xA;    # 请求所有需要的字段（新增estimated_time获取）&#xA;    url = (&#xA;        f&amp;#34;http://{MOONRAKER_IP}:{MOONRAKER_PORT}/printer/objects/query?&amp;#34;&#xA;        &amp;#34;print_stats=filename,state,print_duration,total_duration,filament_used,estimated_time&amp;amp;&amp;#34;  # 新增estimated_time&#xA;        &amp;#34;virtual_sdcard=progress,remaining_time,file_position,metadata,estimated_time&amp;amp;&amp;#34;  # 新增estimated_time&#xA;        &amp;#34;gcode_move=current_layer,position,extruder,absolute_coordinates&amp;amp;&amp;#34;&#xA;        &amp;#34;heater_bed=temperature,target,power&amp;amp;&amp;#34;&#xA;        &amp;#34;extruder=temperature,target,power&amp;amp;&amp;#34;&#xA;        &amp;#34;extruder1=temperature,target,power&amp;amp;&amp;#34;&#xA;        &amp;#34;extruder2=temperature,target,power&amp;amp;&amp;#34;&#xA;        &amp;#34;extruder3=temperature,target,power&amp;amp;&amp;#34;&#xA;        &amp;#34;filament_switch_sensor=enabled&amp;amp;&amp;#34;&#xA;        &amp;#34;configfile=settings&amp;amp;&amp;#34;&#xA;        &amp;#34;layer_display=current_layer,total_layers&amp;#34;  # 添加layer_display支持&#xA;    )&#xA;&#xA;    try:&#xA;        async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=TIMEOUT)) as session:&#xA;            async with session.get(url) as resp:&#xA;                if resp.status == 200:&#xA;                    return await resp.json()&#xA;                else:&#xA;                    print(f&amp;#34;{Colors.YELLOW}⚠️ 获取打印机数据失败，状态码: {resp.status}{Colors.RESET}&amp;#34;)&#xA;                    return {}&#xA;    except Exception as e:&#xA;        print(f&amp;#34;{Colors.RED}⚠️ 获取打印机数据异常: {str(e)}{Colors.RESET}&amp;#34;)&#xA;        return {}&#xA;&#xA;async def main():&#xA;    &amp;#34;&amp;#34;&amp;#34;主程序：显示完整的打印机数据（材料数据间隔刷新）&amp;#34;&amp;#34;&amp;#34;&#xA;    init_console()&#xA;    print(f&amp;#34;{Colors.BOLD}{Colors.CYAN}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;)&#xA;    print(f&amp;#34;{Colors.BOLD}{Colors.CYAN}📡 3D打印机监控系统 v1.0 | 连接地址: {MOONRAKER_IP}:{MOONRAKER_PORT}{Colors.RESET}&amp;#34;)&#xA;    print(f&amp;#34;{Colors.BOLD}{Colors.CYAN}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;)&#xA;    &#xA;    # 初始化变量&#xA;    refresh_counter = 0  # 刷新计数器&#xA;    filament_info = None  # 料盘数据缓存&#xA;    &#xA;    # 首次启动强制获取料盘数据&#xA;    print(f&amp;#34;\n{Colors.BLUE}🔄 首次获取料盘数据...{Colors.RESET}&amp;#34;)&#xA;    filament_info = await get_filament_info()&#xA;    print(f&amp;#34;{Colors.GREEN}✅ 料盘数据初始化完成{Colors.RESET}&amp;#34;)&#xA;    &#xA;    while True:&#xA;        # ========== 1. 料盘数据间隔刷新逻辑 ==========&#xA;        refresh_counter += 1&#xA;        # 每MATERIAL_REFRESH_INTERVAL次刷新才获取一次料盘数据&#xA;        if refresh_counter &amp;gt;= MATERIAL_REFRESH_INTERVAL:&#xA;            print(f&amp;#34;\n{Colors.BLUE}🔄 达到{MATERIAL_REFRESH_INTERVAL}次刷新，更新料盘数据...{Colors.RESET}&amp;#34;)&#xA;            filament_info = await get_filament_info()&#xA;            refresh_counter = 0  # 重置计数器&#xA;            print(f&amp;#34;{Colors.GREEN}✅ 料盘数据更新完成{Colors.RESET}&amp;#34;)&#xA;        &#xA;        # ========== 2. 实时获取核心数据（进度、时间等） ==========&#xA;        printer_data_task = asyncio.create_task(get_full_printer_data())&#xA;        data = await printer_data_task&#xA;        &#xA;        status = safe_get(data, [&amp;#34;result&amp;#34;, &amp;#34;status&amp;#34;], {})&#xA;        current_time = datetime.now().strftime(&amp;#34;%Y-%m-%d %H:%M:%S&amp;#34;)&#xA;        &#xA;        # 基础打印信息&#xA;        print_stats = safe_get(status, [&amp;#34;print_stats&amp;#34;], {})&#xA;        print_state = safe_get(print_stats, [&amp;#34;state&amp;#34;], &amp;#34;standby&amp;#34;)&#xA;        print_file = safe_get(print_stats, [&amp;#34;filename&amp;#34;], &amp;#34;无&amp;#34;) if print_state == &amp;#34;printing&amp;#34; else &amp;#34;无&amp;#34;&#xA;        # 超精简文件名（仅保留前15字符）&#xA;        short_filename = print_file.split(&amp;#34;/&amp;#34;)[-1] if print_file != &amp;#34;无&amp;#34; else &amp;#34;无&amp;#34;&#xA;        if len(short_filename) &amp;gt; 15:&#xA;            short_filename = short_filename[:12] + &amp;#34;...&amp;#34;&#xA;        &#xA;        print_duration = safe_get(print_stats, [&amp;#34;print_duration&amp;#34;], 0)&#xA;        total_duration = safe_get(print_stats, [&amp;#34;total_duration&amp;#34;], 0)&#xA;        filament_used = safe_get(print_stats, [&amp;#34;filament_used&amp;#34;], 0)&#xA;        &#xA;        # 进度和时间&#xA;        virtual_sd = safe_get(status, [&amp;#34;virtual_sdcard&amp;#34;], {})&#xA;        progress = safe_float(safe_get(virtual_sd, [&amp;#34;progress&amp;#34;], 0)) * 100&#xA;        remaining_time = safe_get(virtual_sd, [&amp;#34;remaining_time&amp;#34;], 0)&#xA;        file_position = safe_get(virtual_sd, [&amp;#34;file_position&amp;#34;], 0)&#xA;        &#xA;        # 获取Klipper网页同款切片剩余时间&#xA;        slicer_remaining_time = 0&#xA;        slicer_total_time = 0&#xA;        &#xA;        # 方案1: 优先从print_stats获取&#xA;        print_stats_estimated = safe_get(print_stats, [&amp;#34;estimated_time&amp;#34;], 0)&#xA;        if print_stats_estimated &amp;gt; 0 and print_state == &amp;#34;printing&amp;#34;:&#xA;            slicer_total_time = print_stats_estimated&#xA;            slicer_remaining_time = max(0, slicer_total_time - print_duration)&#xA;        &#xA;        # 方案2: 从virtual_sdcard获取备用&#xA;        elif safe_get(virtual_sd, [&amp;#34;estimated_time&amp;#34;], 0) &amp;gt; 0 and print_state == &amp;#34;printing&amp;#34;:&#xA;            slicer_total_time = safe_get(virtual_sd, [&amp;#34;estimated_time&amp;#34;], 0)&#xA;            slicer_remaining_time = max(0, slicer_total_time - print_duration)&#xA;        &#xA;        # 方案3: 从文件元数据获取切片原始预估时间&#xA;        elif print_file != &amp;#34;无&amp;#34;:&#xA;            file_metadata = await get_file_metadata(print_file)&#xA;            slicer_total_time = safe_get(file_metadata, [&amp;#34;result&amp;#34;, &amp;#34;estimated_time&amp;#34;], 0)&#xA;            if slicer_total_time &amp;gt; 0 and print_state == &amp;#34;printing&amp;#34;:&#xA;                slicer_remaining_time = max(0, slicer_total_time - print_duration)&#xA;        &#xA;        # 待机状态重置切片剩余时间&#xA;        if print_state != &amp;#34;printing&amp;#34;:&#xA;            slicer_remaining_time = 0&#xA;            slicer_total_time = 0&#xA;        &#xA;        # 原有剩余时间计算逻辑&#xA;        if remaining_time &amp;lt;= 0 and print_state == &amp;#34;printing&amp;#34; and progress &amp;gt; 0:&#xA;            remaining_time = calculate_remaining_time(print_duration, progress)&#xA;        &#xA;        if remaining_time &amp;lt;= 0 and print_file != &amp;#34;无&amp;#34;:&#xA;            file_metadata = await get_file_metadata(print_file)&#xA;            estimated_time = safe_get(file_metadata, [&amp;#34;result&amp;#34;, &amp;#34;estimated_time&amp;#34;], 0)&#xA;            if estimated_time &amp;gt; 0:&#xA;                remaining_time = estimated_time - print_duration&#xA;        &#xA;        # 层数信息&#xA;        current_layer = 0&#xA;        total_layers = 0&#xA;        &#xA;        layer_display = safe_get(status, [&amp;#34;layer_display&amp;#34;], {})&#xA;        current_layer = safe_int(layer_display.get(&amp;#34;current_layer&amp;#34;), 0)&#xA;        total_layers = safe_int(layer_display.get(&amp;#34;total_layers&amp;#34;), 0)&#xA;        &#xA;        if current_layer == 0:&#xA;            gcode_move = safe_get(status, [&amp;#34;gcode_move&amp;#34;], {})&#xA;            current_layer = safe_int(gcode_move.get(&amp;#34;current_layer&amp;#34;), 0)&#xA;        &#xA;        if total_layers == 0:&#xA;            metadata = safe_get(virtual_sd, [&amp;#34;metadata&amp;#34;], {})&#xA;            total_layers = safe_int(metadata.get(&amp;#34;layer_count&amp;#34;), 0)&#xA;        &#xA;        if total_layers == 0 and print_file != &amp;#34;无&amp;#34;:&#xA;            file_metadata = await get_file_metadata(print_file)&#xA;            total_layers = safe_int(safe_get(file_metadata, [&amp;#34;result&amp;#34;, &amp;#34;layer_count&amp;#34;], 0), 0)&#xA;        &#xA;        if current_layer == 0 and total_layers &amp;gt; 0 and progress &amp;gt; 0 and print_state == &amp;#34;printing&amp;#34;:&#xA;            current_layer = int(total_layers * progress / 100)&#xA;        &#xA;        # 待机状态优化&#xA;        if print_state != &amp;#34;printing&amp;#34;:&#xA;            current_layer = &amp;#34;待机中&amp;#34;&#xA;            total_layers = &amp;#34;待机中&amp;#34;&#xA;            remaining_time = 0&#xA;        else:&#xA;            if current_layer == 0:&#xA;                current_layer = &amp;#34;获取中&amp;#34;&#xA;            if total_layers == 0:&#xA;                total_layers = &amp;#34;未知&amp;#34;&#xA;        &#xA;        # ========== 更新PowerShell标题（核心修复） ==========&#xA;        if print_state == &amp;#34;printing&amp;#34;:&#xA;            # 精简标题格式：核心信息+短格式&#xA;            title = (&#xA;                f&amp;#34;3D打印进度{progress:.0f}% | 剩余{format_seconds_short(slicer_remaining_time)} &amp;#34;&#xA;            )&#xA;        else:&#xA;            # 待机标题精简&#xA;            title = f&amp;#34;3D打印机状态:{print_state.upper()} | {datetime.now().strftime(&amp;#39;%H:%M:%S&amp;#39;)}&amp;#34;&#xA;        &#xA;        # 设置标题&#xA;        set_powershell_title(title)&#xA;        &#xA;        # 热床信息&#xA;        heater_bed = safe_get(status, [&amp;#34;heater_bed&amp;#34;], {})&#xA;        bed_temp = safe_get(heater_bed, [&amp;#34;temperature&amp;#34;], 0)&#xA;        bed_target = safe_get(heater_bed, [&amp;#34;target&amp;#34;], 0)&#xA;        bed_power = safe_get(heater_bed, [&amp;#34;power&amp;#34;], 0) * 100&#xA;        &#xA;        # 挤出机信息&#xA;        extruders_data = []&#xA;        for extruder_name in EXTRUDERS:&#xA;            extruder = safe_get(status, [extruder_name], {})&#xA;            extruders_data.append({&#xA;                &amp;#34;name&amp;#34;: extruder_name,&#xA;                &amp;#34;temp&amp;#34;: safe_get(extruder, [&amp;#34;temperature&amp;#34;], 0),&#xA;                &amp;#34;target&amp;#34;: safe_get(extruder, [&amp;#34;target&amp;#34;], 0),&#xA;                &amp;#34;power&amp;#34;: safe_get(extruder, [&amp;#34;power&amp;#34;], 0) * 100&#xA;            })&#xA;        &#xA;        # ========== 清屏并显示美化后的数据 ==========&#xA;        os.system(&amp;#39;cls&amp;#39; if sys.platform.startswith(&amp;#39;win&amp;#39;) else &amp;#39;clear&amp;#39;)&#xA;        &#xA;        # 构建美化后的输出内容&#xA;        output = [&#xA;            # 头部标题&#xA;            f&amp;#34;{Colors.BOLD}{Colors.CYAN}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;{Colors.BOLD}{Colors.WHITE}📊 3D打印机实时监控 | {MOONRAKER_IP}:{MOONRAKER_PORT} | {current_time}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;{Colors.BOLD}{Colors.CYAN}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;,&#xA;            &amp;#34;&amp;#34;,&#xA;            # 基础信息区域&#xA;            f&amp;#34;{Colors.BOLD}{Colors.WHITE}【基础信息】{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  📄 打印文件: {Colors.PURPLE}{print_file:&amp;lt;50}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  📌 打印状态: {get_status_color(print_state)}{print_state.upper():&amp;lt;10}{Colors.RESET} &amp;#34;&#xA;            f&amp;#34;📈 进度: {Colors.GREEN}{progress:&amp;gt;5.1f}%{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;&amp;#34;, &#xA;            # 层数信息区域&#xA;            # f&amp;#34;{Colors.BOLD}{Colors.WHITE}【打印层数】{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  📑 当前层数: {Colors.YELLOW}{current_layer:&amp;gt;5}{Colors.RESET} / &amp;#34;&#xA;            f&amp;#34;总层数: {Colors.YELLOW}{total_layers:&amp;lt;5}{Colors.RESET}&amp;#34;,&#xA;            # f&amp;#34;{Colors.LIGHT_GRAY}{&amp;#39;-&amp;#39;*90}{Colors.RESET}&amp;#34;,&#xA;            # 热床信息区域&#xA;            # f&amp;#34;{Colors.BOLD}{Colors.WHITE}【热床状态】{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  🔥 当前温度: {Colors.RED}{bed_temp:&amp;gt;5.1f}°C{Colors.RESET} / &amp;#34;&#xA;            f&amp;#34;目标温度: {Colors.RED}{bed_target:&amp;gt;5.1f}°C{Colors.RESET} / &amp;#34;&#xA;            f&amp;#34;功率: {Colors.RED}{bed_power:&amp;gt;5.0f}%{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;&amp;#34;,&#xA;            f&amp;#34;&amp;#34;,&#xA;            # 时间信息区域 - 修改为左对齐&#xA;            f&amp;#34;{Colors.BOLD}{Colors.WHITE}【时间统计】{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  ⏱️  已打印时间: {Colors.BLUE}{format_seconds(print_duration):&amp;lt;15}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  ⏳  计算剩余时间: {Colors.BLUE}{format_seconds(remaining_time):&amp;lt;15}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  🎯  切片剩余时间: {Colors.BLUE}{format_seconds(slicer_remaining_time):&amp;lt;15}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  🕒  切片总耗时: {Colors.BLUE}{format_seconds(slicer_total_time):&amp;lt;15}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  🕒  总计时间: {Colors.BLUE}{format_seconds(total_duration):&amp;lt;15}{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  🧵  已用耗材: {Colors.BLUE}{filament_used:&amp;lt;15.2f}mm{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  📍  文件位置: {Colors.BLUE}{file_position:&amp;lt;15} 字节{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;&amp;#34;, &#xA;            f&amp;#34;&amp;#34;, &#xA;            # 挤出机信息区域&#xA;            f&amp;#34;{Colors.BOLD}{Colors.WHITE}【挤出机温度】{Colors.RESET}&amp;#34;,&#xA;            f&amp;#34;  {&amp;#39;&amp;#39;:&amp;lt;4}{Colors.BOLD}名称{Colors.RESET:&amp;lt;12} {Colors.BOLD}当前温度{Colors.RESET:&amp;lt;12} {Colors.BOLD}目标温度{Colors.RESET:&amp;lt;12} {Colors.BOLD}功率{Colors.RESET:&amp;lt;8}&amp;#34;,&#xA;            f&amp;#34;  {&amp;#39;&amp;#39;:&amp;lt;4}{&amp;#39;-&amp;#39;*12} {&amp;#39;-&amp;#39;*12} {&amp;#39;-&amp;#39;*12} {&amp;#39;-&amp;#39;*8}&amp;#34;,&#xA;        ]&#xA;        &#xA;        # 添加挤出机数据（美化对齐）&#xA;        for ext in extruders_data:&#xA;            temp_color = Colors.RED if ext[&amp;#34;temp&amp;#34;] &amp;gt; 0 else Colors.WHITE&#xA;            target_color = Colors.ORANGE if ext[&amp;#34;target&amp;#34;] &amp;gt; 0 else Colors.WHITE&#xA;            power_color = Colors.YELLOW if ext[&amp;#34;power&amp;#34;] &amp;gt; 0 else Colors.WHITE&#xA;            &#xA;            output.append(&#xA;                f&amp;#34;  {&amp;#39;&amp;#39;:&amp;lt;4}{ext[&amp;#39;name&amp;#39;]:&amp;lt;12} {temp_color}{ext[&amp;#39;temp&amp;#39;]:&amp;gt;8.1f}°C{Colors.RESET} &amp;#34;&#xA;                f&amp;#34;{target_color}{ext[&amp;#39;target&amp;#39;]:&amp;gt;10.1f}°C{Colors.RESET} &amp;#34;&#xA;                f&amp;#34;{power_color}{ext[&amp;#39;power&amp;#39;]:&amp;gt;8.0f}%{Colors.RESET}&amp;#34;&#xA;            )&#xA;        &#xA;        # 耗材信息（最下方，美化）&#xA;        output.extend([&#xA;            f&amp;#34;&amp;#34;,&#xA;            f&amp;#34;&amp;#34;, &#xA;            f&amp;#34;{Colors.BOLD}{Colors.WHITE}【耗材信息】&amp;#34;&#xA;        ])&#xA;        &#xA;        # 添加料盘数据（使用缓存，美化显示）&#xA;        if filament_info and filament_info[&amp;#34;multi_filament&amp;#34;]:&#xA;            for fil in filament_info[&amp;#34;multi_filament&amp;#34;]:&#xA;                color_display = fil[&amp;#39;color&amp;#39;]&#xA;                # 给颜色名称添加对应颜色&#xA;                if fil[&amp;#39;color&amp;#39;] in [&amp;#34;红色&amp;#34;, &amp;#34;绿色&amp;#34;, &amp;#34;蓝色&amp;#34;, &amp;#34;黄色&amp;#34;, &amp;#34;紫色&amp;#34;, &amp;#34;青色&amp;#34;, &amp;#34;白色&amp;#34;, &amp;#34;黑色&amp;#34;]:&#xA;                    color_map = {&#xA;                        &amp;#34;红色&amp;#34;: Colors.RED,&#xA;                        &amp;#34;绿色&amp;#34;: Colors.GREEN,&#xA;                        &amp;#34;蓝色&amp;#34;: Colors.BLUE,&#xA;                        &amp;#34;黄色&amp;#34;: Colors.YELLOW,&#xA;                        &amp;#34;紫色&amp;#34;: Colors.PURPLE,&#xA;                        &amp;#34;青色&amp;#34;: Colors.CYAN,&#xA;                        &amp;#34;白色&amp;#34;: Colors.WHITE,&#xA;                        &amp;#34;黑色&amp;#34;: Colors.BLACK&#xA;                    }&#xA;                    color_display = f&amp;#34;{color_map[fil[&amp;#39;color&amp;#39;]]}{fil[&amp;#39;color&amp;#39;]}{Colors.RESET}&amp;#34;&#xA;                &#xA;                output.append(&#xA;                    f&amp;#34;  {&amp;#39;&amp;#39;:&amp;lt;4}{fil[&amp;#39;index&amp;#39;]:&amp;lt;6} {fil[&amp;#39;type&amp;#39;]:&amp;lt;15} {color_display:&amp;lt;15} {fil[&amp;#39;diameter&amp;#39;]:&amp;lt;10}&amp;#34;&#xA;                )&#xA;        &#xA;        output.append(&amp;#34;&amp;#34;)&#xA;        &#xA;        # 打印所有美化后的信息&#xA;        for line in output:&#xA;            print(line, flush=True)&#xA;        &#xA;        # 倒计时刷新（美化显示）&#xA;        for i in range(QUERY_INTERVAL, 0, -1):&#xA;            print(f&amp;#34;\r{Colors.BLUE}⏳ 下次刷新倒计时: {i}秒{Colors.RESET}&amp;#34;, end=&amp;#34;&amp;#34;, flush=True)&#xA;            await asyncio.sleep(1)&#xA;&#xA;# 补充缺失的颜色常量&#xA;Colors.LIGHT_GRAY = &amp;#34;\033[37m&amp;#34;&#xA;Colors.GRAY = &amp;#34;\033[90m&amp;#34;&#xA;Colors.BLACK = &amp;#34;\033[30m&amp;#34;&#xA;Colors.ORANGE = &amp;#34;\033[38;5;208m&amp;#34;&#xA;&#xA;# ===================== 启动程序 =====================&#xA;if __name__ == &amp;#34;__main__&amp;#34;:&#xA;    # 安装依赖&#xA;    try:&#xA;        import websockets&#xA;    except ImportError:&#xA;        print(f&amp;#34;{Colors.RED}⚠️ 缺少websockets依赖，正在安装...{Colors.RESET}&amp;#34;)&#xA;        os.system(&amp;#34;pip install websockets&amp;#34; if sys.platform.startswith(&amp;#39;win&amp;#39;) else &amp;#34;pip3 install websockets&amp;#34;)&#xA;        import websockets&#xA;    &#xA;    try:&#xA;        import aiohttp&#xA;    except ImportError:&#xA;        print(f&amp;#34;{Colors.RED}⚠️ 缺少aiohttp依赖，正在安装...{Colors.RESET}&amp;#34;)&#xA;        os.system(&amp;#34;pip install aiohttp&amp;#34; if sys.platform.startswith(&amp;#39;win&amp;#39;) else &amp;#34;pip3 install aiohttp&amp;#34;)&#xA;        import aiohttp&#xA;    &#xA;    try:&#xA;        asyncio.run(main())&#xA;    except KeyboardInterrupt:&#xA;        # 退出时恢复标题&#xA;        set_powershell_title(&amp;#34;PowerShell&amp;#34;)&#xA;        os.system(&amp;#39;cls&amp;#39; if sys.platform.startswith(&amp;#39;win&amp;#39;) else &amp;#39;clear&amp;#39;)&#xA;        print(f&amp;#34;\n{Colors.PURPLE}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;)&#xA;        print(f&amp;#34;{Colors.PURPLE}👋 3D打印机监控已退出 | 感谢使用{Colors.RESET}&amp;#34;)&#xA;        print(f&amp;#34;{Colors.PURPLE}{&amp;#39;=&amp;#39;*100}{Colors.RESET}&amp;#34;)&#xA;    except Exception as e:&#xA;        # 异常时恢复标题&#xA;        set_powershell_title(&amp;#34;PowerShell&amp;#34;)&#xA;        print(f&amp;#34;\n{Colors.RED}❌ 程序异常: {type(e).__name__} - {e}{Colors.RESET}&amp;#34;)&#xA;        input(f&amp;#34;{Colors.YELLOW}\n按回车退出...{Colors.RESET}&amp;#34;)&#xA;&lt;/code&gt;&lt;/pre&gt;</description>
    </item>
    <item>
      <title>Snapmaker U1 3D打印机研究学习</title>
      <link>/it/2026/020410-u1/</link>
      <pubDate>Wed, 04 Feb 2026 02:51:59 +0000</pubDate>
      <guid>/it/2026/020410-u1/</guid>
      <description>&lt;p&gt;终于还是购了Snapmaker U1，选择了性价比。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../020410-u1-01.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;一步一步封箱中。&lt;/p&gt;&#xA;&lt;p&gt;&lt;img src=&#34;../020410-u1-02.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;问题：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;每次都检测平台有异物，点击继续会重置，就可以继续打印了。&lt;/li&gt;&#xA;&lt;li&gt;因为使用的Orca版本较低（比拓竹），所以别家的（拓竹）的模型不能直接打开使用。想办法使用至少会丢失颜色。&lt;/li&gt;&#xA;&lt;li&gt;以下的报错不知道如何处理：相对挤出机寻址要求在每层重置挤出机位置，以防止浮点精度损失。将G92E0添加到图层代码中&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;以上问题已解决：&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
