[{"content":" Day 1 学习笔记\n如果你学过计算机网络，那你一定听过 SDN（Software-Defined Networking，软件定义网络） 这个词。SDN 到底是什么？它解决了什么问题？它和传统网络有什么不同？\n这是一个 SDN 新手入门笔记，我将从初学者的角度，尽量用通俗的方式把这些问题讲清楚。\n一、为什么需要 SDN 要理解 SDN，首先要理解传统网络遇到了什么问题。\n1.1 传统网络：控制与转发紧耦合 在传统网络中，每台网络设备（路由器、交换机）都是一个独立的黑盒，同时负责两件事：\n控制平面：运行路由协议（如 OSPF、BGP），计算转发路径，维护路由表 数据平面：根据路由表，实际转发数据包 这两个平面紧密耦合在一台设备内部。每台设备各自为政，各自运行路由协议，各自做转发决策。\n1.2 这种设计带来了什么问题 问题 具体表现 配置复杂 每台设备要逐台手动配置 CLI 命令，无法批量管理 扩展困难 新增功能需要更换硬件，无法快速迭代 缺乏全局视角 每台设备只看到局部拓扑，无法做全局最优路由 厂商锁定 不同厂商的设备命令不通用，迁移成本极高 响应缓慢 网络策略变更需要逐个设备修改，耗时数周甚至数月 一句话总结：传统网络就像一个没有指挥的乐队，每个乐手各吹各的调。\n1.3 业务需求在倒逼变革 随着云计算、大数据、移动互联网的爆发，网络面临前所未有的压力：\n数据中心需要动态调整流量路径 租户需要按需获取网络资源 安全策略需要快速部署和更新 传统网络的僵化架构已经无法满足这些需求。正是在这样的背景下，SDN 应运而生。\n二、SDN 的核心思想 SDN 的核心思想就一句话：\n把网络设备的控制权抽出来，集中到一个地方统一管理，数据设备只负责埋头转发。\n打个比方：\n传统网络 = 每个路口都有一个独立的交警，各自指挥交通 SDN = 一个中央交通指挥中心 + 遍布路口的信号灯 中央指挥中心掌握全局路况，可以动态调整信号灯，实现全局最优的流量调度。\n三、SDN 的三层架构 SDN 采用了清晰的三层架构，这是理解 SDN 最关键的知识点：\n┌─────────────────────────────────────────────────┐ │ 应用层 (Application Layer) │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ 流量工程 │ │ 安全防护 │ │ 负载均衡 │ ... │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ └───────────┬┼───────────┘ │ │ ││ 北向接口 (Northbound API) │ ├────────────────────┼────────────────────────────┤ │ 控制层 (Control Layer) │ │ ┌──────────────────────────────────────┐ │ │ │ SDN 控制器 (Controller) │ │ │ │ (如 OpenDaylight, ONOS, Ryu...) │ │ │ └────────────────┬─────────────────────┘ │ │ │ 南向接口 (Southbound API) │ ├───────────────────┼────────────────────────────┤ │ 数据层 (Data Layer) │ │ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ 交换机│ │ 路由器│ │ 防火墙│ │ 中间盒│ ... │ │ └──────┘ └──────┘ └──────┘ └──────┘ │ └─────────────────────────────────────────────────┘ 3.1 数据层 也叫基础设施层，由各种网络设备组成。这些设备不再需要运行复杂的路由协议，只需根据控制器下发的**流表（Flow Table）**来转发数据包。可以说是\u0026quot;无脑转发\u0026quot;，但效率极高。\n3.2 控制层 这是 SDN 的大脑。SDN 控制器是整个网络的核心，它：\n拥有网络的全局视图（全网拓扑、链路状态、流量情况） 负责计算转发路径、下发流表到数据层设备 通过南向接口与数据层通信 通过北向接口向上层应用开放能力 常见的 SDN 控制器有：OpenDaylight、ONOS、Ryu、Floodlight 等。\n3.3 应用层 各种网络应用运行在这一层，通过北向接口调用控制器的能力，实现：\n流量工程：动态调整流量路径，避免拥塞 网络安全：快速部署访问控制策略，检测异常流量 负载均衡：根据实时流量分发请求 网络虚拟化：为多租户创建隔离的虚拟网络 应用层让网络变得像软件一样可编程——想加什么功能，写个应用就行了。\n3.4 两个关键接口 接口 方向 作用 代表协议 南向接口 控制层 → 数据层 控制器向交换机下发流表 OpenFlow（最主流） 北向接口 应用层 → 控制层 应用调用控制器能力 REST API（常见） 东西向接口 控制器之间 多控制器协同 自定义协议 四、SDN 的三个核心特征 控制与转发分离 这是 SDN 最根本的特征。控制逻辑从设备中抽离出来，集中到控制器中执行，设备只做简单的转发。\n集中控制 控制器拥有全网视角，可以做出全局最优的决策。相比于传统网络每台设备各自为政，集中控制可以：\n避免路由环路 实现全局负载均衡 快速响应网络变化 可编程性 网络行为可以通过软件编程来定义和修改。想调整路由策略？写一行代码就行，不用再逐台登录设备敲命令。\n五、SDN 的发展脉络 时代 特点 传统网络 控制与转发紧耦合，每台设备独立决策，手动配置 SDN (OpenFlow) 控制与转发分离，集中控制，OpenFlow 南向协议 P4 可编程数据面 不仅控制面可编程，数据面也可编程，协议无关 SDN 是从传统网络到可编程网络的关键一步。而下一代技术 P4 则更进一步，让数据平面本身也变得可编程（这将是第二周学习的内容）。\n六、用三句话概括 SDN 传统网络：每个路口各有一个交警各自指挥，互不通气，容易堵车。 SDN：撤掉所有路口的交警，换成一个中央交通指挥中心 + 信号灯。指挥中心看到全局路况，统一调度。 好处：堵车时能快速调整信号灯配时，想改交通规则不用换信号灯硬件，写个软件就行。 七、今日学习总结 项目 内容 阅读 《深度解析SDN》第1~2章 精读综述 A Survey of SDN: Past, Present, and Future（Abstract + Section 1-2） 快读综述 A Survey on Software-Defined Networking（Abstract + Conclusion） 核心收获 理解 SDN 诞生的背景、三层架构、控制与转发分离的思想 待深入 明天继续学习 SDN 各层职责的细节和南向/北向接口 参考资料 《深度解析SDN：利益、战略、技术、实践》—— 第1~2章 Kreutz, D. et al. \u0026ldquo;A Survey of Software-Defined Networking: Past, Present, and Future of Programmable Networks.\u0026rdquo; IEEE Communications Surveys \u0026amp; Tutorials, 2015. Akyildiz, I. F. et al. \u0026ldquo;A Survey on Software-Defined Networking.\u0026rdquo; IEEE Network, 2014. SDNLAB SDN 指南 本文是「SDN 入门学习计划」Day 1 的学习笔记。\n学习路线：Day 1 SDN 是什么 → Day 2 SDN 架构深入 → Day 3 OpenFlow 协议详解 → Day 4 Mininet 实验 → \u0026hellip;\n","permalink":"https://sukkk-zcy.github.io/research/sdn-01-sdn-introduction/","summary":"用最通俗的方式讲清楚 SDN 是什么——从传统网络的困境到 SDN 的三层架构，再到可编程网络的未来。","title":"SDN 是什么"},{"content":"简介 Nushell Nushell（简称 Nu）是一个全新的 Shell，与传统 Shell 最大的区别在于它将一切视为结构化数据（表格、列表、记录），而不是纯文本流。这意味着你可以像操作数据库一样操作命令行输出：\n1ls | where size \u0026gt; 1mb | sort-by modified Nu 同时支持 Windows / macOS / Linux，当前最新稳定版为 0.113.1。\nStarship Starship 是一个用 Rust 编写的 Shell 提示符工具，速度极快（每次渲染约 1-5ms），使用单一的 TOML 配置文件，可以跨 Shell（Bash、Zsh、Fish、PowerShell、Nushell）共享同一套配置。当前最新版本为 1.25.1。\n安装 Nushell 方式一：winget（推荐 / Windows） 1winget install nushell 方式二：Scoop 1scoop install nu 方式三：cargo 1cargo install nu --locked 安装完成后，在终端输入 nu 即可进入 Nushell。\n设置 Nushell 为默认 Shell 在 Windows Terminal 中，进入设置 → 配置文件 → 添加新配置文件，命令行指向 Nu 可执行文件路径：\nC:\\Users\\\u0026lt;用户名\u0026gt;\\AppData\\Local\\Programs\\nu\\bin\\nu.exe 也可以在 PowerShell 中通过 $PROFILE 添加：\n1nu 配置 Nushell Nushell 的配置文件位于 %AppData%\\nushell\\config.nu，在 Nu 中可以用以下命令直接打开：\n1config nu 以下是实际使用的配置（C:\\Users\\29016\\AppData\\Roaming\\nushell\\config.nu）：\n1# Nushell Configuration 2 3use std 4 5# ─── Starship Prompt ─────────────────────────────────────────── 6$env.STARSHIP_SHELL = \u0026#34;nu\u0026#34; 7$env.PROMPT_COMMAND = { || 8 starship prompt --cmd-duration $env.CMD_DURATION_MS $\u0026#34;--status=($env.LAST_EXIT_CODE)\u0026#34; 9} 10$env.PROMPT_COMMAND_RIGHT = \u0026#34;\u0026#34; 11$env.PROMPT_INDICATOR = \u0026#34;\u0026#34; 12$env.PROMPT_INDICATOR_VI_INSERT = \u0026#34;: \u0026#34; 13$env.PROMPT_INDICATOR_VI_NORMAL = \u0026#34;〉\u0026#34; 14$env.PROMPT_MULTILINE_INDICATOR = \u0026#34;::: \u0026#34; 15 16# ─── Editor ─────────────────────────────────────────────────── 17$env.EDITOR = \u0026#34;notepad\u0026#34; 18$env.VISUAL = \u0026#34;notepad\u0026#34; 19 20# ─── Aliases ────────────────────────────────────────────────── 21alias ll = ls -l 22alias la = ls -a 23alias lla = ls -la 24alias g = git 25alias c = clear 26alias vim = nvim 27alias python = python3 28 29# ─── PATH ───────────────────────────────────────────────────── 30$env.Path = ($env.Path | split row (char esep) | append \u0026#39;C:\\Users\\29016\\AppData\\Local\\Programs\\nu\\bin\u0026#39;) 31 32# ─── Default Configurations ────────────────────────────────── 33$env.config.show_banner = false # 隐藏启动 banner 34$env.config.history.max_size = 10000 配置说明 配置项 说明 $env.STARSHIP_SHELL = \u0026quot;nu\u0026quot; 告诉 Starship 当前 Shell 是 Nushell $env.PROMPT_COMMAND 设置 Starship 为主提示符，传递持续时间和退出状态码 $env.PROMPT_INDICATOR 普通模式下提示符为空（由 Starship 接管） $env.PROMPT_INDICATOR_VI_INSERT VI 插入模式提示符设为 : $env.PROMPT_INDICATOR_VI_NORMAL VI 普通模式提示符 show_banner = false 隐藏 Nushell 启动时的 logo 横幅 history.max_size = 10000 限制历史记录最大条数 别名一览 别名 等价命令 ll ls -l la ls -a lla ls -la g git c clear vim nvim python python3 安装 Starship 方式一：winget（Windows） 1winget install starship 方式二：官方安装脚本 1curl -sS https://starship.rs/install.sh | sh 验证安装 1starship --version 2# starship 1.25.1 配置 Starship Starship 的配置文件位于 ~\\.config\\starship.toml（即 C:\\Users\\\u0026lt;用户名\u0026gt;\\.config\\starship.toml）。\n使用预设快速起步 Starship 内置了 12 个官方预设，推荐使用 Gruvbox Rainbow：\n1starship preset gruvbox-rainbow -o ~\\.config\\starship.toml 其他热门预设：\n预设 风格 gruvbox-rainbow 温暖大地色 + Powerline 箭头（推荐） tokyo-night 深色蓝紫冷色调 pastel-powerline 粉彩 Powerline 风格 catppuccin-powerline Catppuccin 主题 pure-preset 极简单行提示符 no-runtime-versions 不显示运行时版本信息 自定义配置 以下是在 Gruvbox Rainbow 预设基础上实际使用的完整配置：\n1\u0026#34;$schema\u0026#34; = \u0026#39;https://starship.rs/config-schema.json\u0026#39; 2 3scan_timeout = 500 4 5format = \u0026#34;\u0026#34;\u0026#34; 6[](color_orange)\\ 7$os\\ 8$username\\ 9[](bg:color_yellow fg:color_orange)\\ 10$directory\\ 11[](fg:color_yellow bg:color_aqua)\\ 12$git_branch\\ 13$git_status\\ 14[](fg:color_aqua bg:color_blue)\\ 15$c\\ 16$cpp\\ 17$rust\\ 18$golang\\ 19$nodejs\\ 20$bun\\ 21$php\\ 22$java\\ 23$kotlin\\ 24$haskell\\ 25$python\\ 26[](fg:color_blue bg:color_bg3)\\ 27$docker_context\\ 28$conda\\ 29$pixi\\ 30[](fg:color_bg3 bg:color_bg1)\\ 31$time\\ 32[ ](fg:color_bg1)\\ 33$line_break$character\u0026#34;\u0026#34;\u0026#34; 34 35palette = \u0026#39;gruvbox_dark\u0026#39; 36 37[palettes.gruvbox_dark] 38color_fg0 = \u0026#39;#fbf1c7\u0026#39; 39color_bg1 = \u0026#39;#3c3836\u0026#39; 40color_bg3 = \u0026#39;#665c54\u0026#39; 41color_blue = \u0026#39;#458588\u0026#39; 42color_aqua = \u0026#39;#689d6a\u0026#39; 43color_green = \u0026#39;#98971a\u0026#39; 44color_orange = \u0026#39;#d65d0e\u0026#39; 45color_purple = \u0026#39;#b16286\u0026#39; 46color_red = \u0026#39;#cc241d\u0026#39; 47color_yellow = \u0026#39;#d79921\u0026#39; 48 49[os] 50disabled = false 51style = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; 52 53[os.symbols] 54Windows = \u0026#34;󰍲\u0026#34; 55Ubuntu = \u0026#34;󰕈\u0026#34; 56# ... 其他系统图标 57 58[username] 59show_always = true 60style_user = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; 61style_root = \u0026#34;bg:color_orange fg:color_fg0\u0026#34; 62format = \u0026#39;[ $user ]($style)\u0026#39; 63 64[directory] 65style = \u0026#34;fg:color_fg0 bg:color_yellow\u0026#34; 66format = \u0026#34;[ $path ]($style)\u0026#34; 67truncation_length = 3 68truncation_symbol = \u0026#34;…/\u0026#34; 69 70[directory.substitutions] 71\u0026#34;Documents\u0026#34; = \u0026#34;󰈙 \u0026#34; 72\u0026#34;Downloads\u0026#34; = \u0026#34; \u0026#34; 73\u0026#34;Music\u0026#34; = \u0026#34;󰝚 \u0026#34; 74\u0026#34;Pictures\u0026#34; = \u0026#34; \u0026#34; 75\u0026#34;Developer\u0026#34; = \u0026#34;󰲋 \u0026#34; 76 77[git_branch] 78symbol = \u0026#34;\u0026#34; 79style = \u0026#34;bg:color_aqua\u0026#34; 80format = \u0026#39;[[ $symbol $branch ](fg:color_fg0 bg:color_aqua)]($style)\u0026#39; 81 82[git_status] 83style = \u0026#34;bg:color_aqua\u0026#34; 84format = \u0026#39;[[($all_status$ahead_behind )](fg:color_fg0 bg:color_aqua)]($style)\u0026#39; 85 86# 语言模块（统一蓝底风格） 87[nodejs] 88symbol = \u0026#34;\u0026#34; 89style = \u0026#34;bg:color_blue\u0026#34; 90format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 91 92[bun] 93symbol = \u0026#34;\u0026#34; 94style = \u0026#34;bg:color_blue\u0026#34; 95format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 96 97[c] 98symbol = \u0026#34; \u0026#34; 99style = \u0026#34;bg:color_blue\u0026#34; 100format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 101 102[cpp] 103symbol = \u0026#34; \u0026#34; 104style = \u0026#34;bg:color_blue\u0026#34; 105format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 106 107[rust] 108symbol = \u0026#34;\u0026#34; 109style = \u0026#34;bg:color_blue\u0026#34; 110format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 111 112[golang] 113symbol = \u0026#34;\u0026#34; 114style = \u0026#34;bg:color_blue\u0026#34; 115format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 116 117[python] 118symbol = \u0026#34;\u0026#34; 119style = \u0026#34;bg:color_blue\u0026#34; 120format = \u0026#39;[[ $symbol( $version) ](fg:color_fg0 bg:color_blue)]($style)\u0026#39; 121 122[docker_context] 123symbol = \u0026#34;\u0026#34; 124style = \u0026#34;bg:color_bg3\u0026#34; 125format = \u0026#39;[[ $symbol( $context) ](fg:#83a598 bg:color_bg3)]($style)\u0026#39; 126 127[conda] 128style = \u0026#34;bg:color_bg3\u0026#34; 129format = \u0026#39;[[ $symbol( $environment) ](fg:#83a598 bg:color_bg3)]($style)\u0026#39; 130 131[pixi] 132style = \u0026#34;bg:color_bg3\u0026#34; 133format = \u0026#39;[[ $symbol( $version)( $environment) ](fg:color_fg0 bg:color_bg3)]($style)\u0026#39; 134 135[time] 136disabled = false 137time_format = \u0026#34;%R\u0026#34; 138style = \u0026#34;bg:color_bg1\u0026#34; 139format = \u0026#39;[[  $time ](fg:color_fg0 bg:color_bg1)]($style)\u0026#39; 140 141[line_break] 142disabled = false 143 144[character] 145disabled = false 146success_symbol = \u0026#39;[](bold fg:color_green)\u0026#39; 147error_symbol = \u0026#39;[](bold fg:color_red)\u0026#39; 148vimcmd_symbol = \u0026#39;[](bold fg:color_green)\u0026#39; 149vimcmd_replace_one_symbol = \u0026#39;[](bold fg:color_purple)\u0026#39; 150vimcmd_replace_symbol = \u0026#39;[](bold fg:color_purple)\u0026#39; 151vimcmd_visual_symbol = \u0026#39;[](bold fg:color_yellow)\u0026#39; 配置结构解析 整条提示符由 Powerline 风格的分段箭头连接，自左向右依次为：\n模块 背景色 显示内容 $os 橙色 当前操作系统图标 $username 橙色 用户名 $directory 黄色 当前目录路径（截断至 3 级） $git_branch 青色 Git 分支名 $git_status 青色 Git 状态（修改/暂存/冲突/ ahead/behind） 语言模块 蓝色 C/C++/Rust/Go/Node.js/Bun/PHP/Java/Kotlin/Haskell/Python 版本 $docker_context 灰 3 Docker 上下文 $conda 灰 3 Conda 环境 $pixi 灰 3 Pixi 环境 $time 灰 1 当前时间（24h） $character — 提示符箭头（绿色成功 / 红色失败） 检查配置 Starship 提供了方便的调试命令：\n1# 查看当前渲染的配置值 2starship config 3 4# 查看各模块渲染耗时 5starship timings 6 7# 查看预设默认配置 8starship preset gruvbox-rainbow 9 10# 检查 TOML 语法（需要 taplo） 11taplo lint ~\\.config\\starship.toml Nushell + Starship 集成 将 Nushell 和 Starship 结合使用的关键配置在 config.nu 中：\n1$env.STARSHIP_SHELL = \u0026#34;nu\u0026#34; 2$env.PROMPT_COMMAND = { || 3 starship prompt --cmd-duration $env.CMD_DURATION_MS $\u0026#34;--status=($env.LAST_EXIT_CODE)\u0026#34; 4} 这段配置做了三件事：\n$env.STARSHIP_SHELL = \u0026quot;nu\u0026quot; — 设置环境变量，Starship 据此加载 Nushell 特有的提示符模块 $env.PROMPT_COMMAND — 定义一个闭包，每次绘制提示符时调用 starship prompt，并传入命令执行时间 CMD_DURATION_MS 和上一条命令的退出状态码 LAST_EXIT_CODE 清空其它提示符变量 — PROMPT_COMMAND_RIGHT、PROMPT_INDICATOR 等设为空，完全由 Starship 接管渲染 额外技巧 1. 目录图标替换 Starship 支持将常见目录名替换为 Nerd Font 图标，当前配置中的替换表：\n原目录 替换图标 Documents 󰈙 Downloads  Music 󰝚 Pictures  Developer 󰲋 2. 提示符颜色随退出状态变化 Starship 的 $character 模块会根据上一条命令的退出码自动变色：成功为绿色 ，失败为红色 ，无需额外配置。\n3. 调试慢提示符 如果感觉提示符响应变慢，使用 starship timings 查看各模块耗时。常见的耗时模块是 git_status，可以设置 scan_timeout = 500（当前已配置）来限制 Git 扫描超时。\n参考 Nushell 官方文档 Nushell GitHub Starship 官方文档 Starship 预设列表 Gruvbox Rainbow 预设 ","permalink":"https://sukkk-zcy.github.io/posts/nushell-starship-guide/","summary":"Nushell 是一个现代结构化 Shell，Starship 是一个跨 Shell 的快速提示符工具，两者搭配使用可以获得美观且高效的终端体验。本文记录从安装到配置的完整流程。","title":"Nushell + Starship 终端环境配置指南"},{"content":"什么是 Blinker？ Blinker 是一套跨硬件、跨平台的物联网解决方案，提供 APP 端、设备端、服务器端支持，使用公有云服务进行数据传输存储。适用于智能家居、数据监测等场景。\nGitHub: https://github.com/blinker-iot/ 官网: https://diandeng.tech/ APP: 点灯科技 APP（iOS / Android） 许可证: MIT License 硬件平台支持 Blinker 目前支持三大主流开发板：\n平台 WiFi BLE NB-IoT GPRS ESP32 ✅ ✅ 内置 ✅ SIM7020/SIM7000 ✅ Air202 ESP8266 ✅ ✅ 外接透传模块 ✅ ✅ Arduino R4 ✅ AT固件 ✅ - - ESP32 凭借内置蓝牙和丰富的外设接口，是目前最推荐的入门选择。\n通信模式 WiFi 和蓝牙不能同时启用，通过宏定义切换：\n1#define WIFIMODE // 启用WiFi模式 2//#define BLEMODE // 启用蓝牙模式 3 4#if defined(WIFIMODE) 5 #define BLINKER_WIFI 6#endif 7#if defined(BLEMODE) 8 #define BLINKER_BLE 9#endif 10 11#include \u0026lt;Blinker.h\u0026gt; // 必须在通信模式宏定义之后 关键规则：#include \u0026lt;Blinker.h\u0026gt; 必须放在通信模式宏定义之后，否则编译会出错。\nWiFi 模式初始化：\n1Blinker.begin(auth, ssid, pswd); BLE 模式初始化（ESP32 内置蓝牙）：\n1Blinker.begin(); 程序结构：九大标准模块 Blinker 标准程序按以下顺序组织：\n程序版本备注 — 记录日期和功能 通信模式宏定义 — WIFIMODE / BLEMODE 切换 包含库 — #include \u0026lt;Blinker.h\u0026gt; 全局变量声明 — WiFi 信息、引脚、状态变量 组件声明 — 必须放在回调函数前面 回调函数 — APP 发来数据时由 Blinker.run() 调用 功能函数 — WiFi 掉线检测等 setup() 初始化 — 开机后只执行一次 loop() 主循环 — 循环执行到关机 这个结构清晰地分离了配置、声明和逻辑，是编写 Blinker 程序的基本骨架。\n组件：APP 端的交互元素 Blinker 提供丰富的内置组件：\n组件 类名 用途 按钮 BlinkerButton 开关、模式切换 滑块 BlinkerSlider 阈值、亮度、速度 数值 BlinkerNumber 显示传感器数据 文本 BlinkerText 显示字符串 RGB BlinkerRGB 颜色控制 摇杆 BlinkerJoystick 方向控制 开关 BlinkerSwitch 内置开关 声明示例：\n1BlinkerButton Button1(\u0026#34;b1\u0026#34;); // 键名 \u0026#34;b1\u0026#34;，对应APP中组件名 2BlinkerSlider Slider1(\u0026#34;s1\u0026#34;); // 键名 \u0026#34;s1\u0026#34; 3BlinkerNumber NUM1(\u0026#34;abcd\u0026#34;); // 键名 \u0026#34;abcd\u0026#34; 4BlinkerText Text1(\u0026#34;1234\u0026#34;); // 键名 \u0026#34;1234\u0026#34; 核心规则：组件声明必须放在回调函数前面，键名必须与 APP 中的组件名完全一致。\n回调函数：设备与 APP 的数据桥梁 按钮回调 1void button1_callback(const String\u0026amp; state) 2{ 3 if (state == \u0026#34;tap\u0026#34;) { b1_state = !b1_state; } // 普通按键：反转 4 else if (state == \u0026#34;on\u0026#34;) { b1_state = true; } // 开关：开启 5 else if (state == \u0026#34;off\u0026#34;) { b1_state = false; } // 开关：关闭 6 else if (state == \u0026#34;press\u0026#34;) { b1_state = true; } // 长按：开启 7 else if (state == \u0026#34;pressup\u0026#34;) { b1_state = false; } // 松开：关闭 8 Button1.print(b1_state ? \u0026#34;on\u0026#34; : \u0026#34;off\u0026#34;); // 立即反馈到APP 9} 重要：不要简化按钮的五种处理，防止 APP 设置不同出现逻辑错误。\n滑块回调 1void slider1_callback(int32_t value) 2{ 3 s1_value = value; // 赋值全局变量即可 4} 注意：滑块不可在回调函数里调用 print，否则拖不动。\n注册回调 1void setup() 2{ 3 Button1.attach(button1_callback); 4 Slider1.attach(slider1_callback); 5 Blinker.attachHeartbeat(heartbeat); 6 Blinker.attachDataStorage(dataStorage); 7} 心跳包：APP 获取设备数据的核心机制 心跳包是 APP 获取设备数据的主要方式，间隔约 30 秒一次。\n1void heartbeat() 2{ 3 Button1.print(b1_state ? \u0026#34;on\u0026#34; : \u0026#34;off\u0026#34;); // 按钮：回调+心跳包双保险 4 Slider1.print(s1_value); // 滑块：仅心跳包 5 NUM1.print(100); // 数值：仅心跳包 6 Text1.print(\u0026#34;Hello Blinker\u0026#34;); // 文本：仅心跳包 7 Blinker.notify(\u0026#34;页面数据已刷新\u0026#34;); // APP内弹窗推送 8} 数据上报位置规则 数据类型 上报位置 原因 按钮状态 回调函数 + 心跳包 即时反馈 + 兜底 滑块值 仅心跳包 回调里 print 会拖不动 数值/文本 仅心跳包 统一管理 关键：print 必须放心跳包回调或按钮回调，不能放 loop()，否则触发 MQTT NOT ALIVE OR MESSAGE LIMIT 错误。\n数据存储与图表 生成传感器数据折线图表：\n1void dataStorage() 2{ 3 Blinker.dataStorage(\u0026#34;data1\u0026#34;, random(0, 120)); 4} 5 6void setup() 7{ 8 Blinker.attachDataStorage(dataStorage); 9} 实时数据（RT_DATA） 实时数据每秒以上一次，适用于高频刷新场景。不支持文本组件，只支持数值组件。\n1void rtData() 2{ 3 Blinker.sendRtData(\u0026#34;data1\u0026#34;, (int32_t)random(0, 120)); 4 Blinker.sendRtData(\u0026#34;data2\u0026#34;, random(0, 120) / (float)1.5); 5 Blinker.printRtData(); // 刷新到 APP 6} 7 8void setup() 9{ 10 Blinker.attachRTData(rtData); 11} WiFi 掉线检测 WiFi 模式下必须实现掉线重启检测：\n1void wifiOfflineMonitor() 2{ 3 if (Blinker.connect()) { 4 offline_millis = 0; 5 offline_flag = false; 6 } 7 else if (!Blinker.connect() \u0026amp;\u0026amp; millis() \u0026gt; 180000) { 8 offline_flag = true; 9 } 10 11 if (millis() \u0026gt; 180000 \u0026amp;\u0026amp; offline_millis \u0026amp;\u0026amp; millis() - offline_millis \u0026gt;= 180000) { 12 ESP.restart(); // 掉线超三分钟重启 13 } 14} 在 loop() 中调用：\n1void loop() 2{ 3 Blinker.run(); 4 wifiOfflineMonitor(); 5} 调试与日志 1void setup() 2{ 3 Serial.begin(115200); 4 BLINKER_DEBUG.stream(Serial); // 调试信息输出到串口 5 BLINKER_DEBUG.debugAll(); // 开启所有调试信息 6 BLINKER_LOG(\u0026#34;版本信息\u0026#34;); // 打印日志 7} 常见错误排查 错误 原因 解决 MQTT NOT ALIVE OR MESSAGE LIMIT print 放在 loop() 中 移到心跳包回调或按钮回调 按钮状态不更新 APP 按钮类型与代码不匹配 保留五种状态处理 滑块拖动不灵敏 回调里调用了 print 滑块 print 只放心跳包 WiFi 连不上 非 2.4GHz / 密码错误 检查频段和密码 授权码错误 引号/空格问题 从点灯 APP 复制 ESP32 Web 配网 ESP32_Blinker_NET 是一个完整的 Web 配网示例项目，支持通过网页配置 WiFi 和 Blinker 授权码。\n工作流程：\n开机读取 EEPROM 中的 WiFi 配置 有配置则尝试连接，无配置则进入配网模式 手机连接热点 ESP32_Config，访问 192.168.4.1 在 Web 页面选择 WiFi、输入密码和授权码 保存后 ESP32 重启，自动连接 核心代码：\n1WIFIConnect wifiManager(\u0026#34;ESP32_Config\u0026#34;); 2 3void setup() { 4 Serial.begin(115200); 5 wifiManager.begin(); 6 if (wifiManager.isConnected()) { 7 Blinker.begin( 8 wifiManager.getAuth().c_str(), 9 wifiManager.getSSID().c_str(), \u0026#34;\u0026#34; 10 ); 11 } 12} 13 14void loop() { 15 wifiManager.loop(); 16 if (wifiManager.isConnected()) Blinker.run(); 17} 完整模板代码 以下是一个包含按钮、滑块、数值、文本、数据存储、心跳包、APP弹窗七大功能的完整模板：\n1#define NOTE \u0026#34;Blinker 完整模板\u0026#34; 2 3#define WIFIMODE 4 5#if defined(WIFIMODE) 6 #define BLINKER_WIFI 7#endif 8 9#include \u0026lt;Blinker.h\u0026gt; 10 11char ssid[] = \u0026#34;WiFi名称\u0026#34;; 12char pswd[] = \u0026#34;WiFi密码\u0026#34;; 13char auth[] = \u0026#34;授权码\u0026#34;; 14 15#define RELAYPIN_1 2 16bool b1_state = false; 17int s1_value = 0; 18 19BlinkerButton Button1(\u0026#34;b1\u0026#34;); 20BlinkerSlider Slider1(\u0026#34;s1\u0026#34;); 21BlinkerNumber NUM1(\u0026#34;abcd\u0026#34;); 22BlinkerText Text1(\u0026#34;1234\u0026#34;); 23 24void button1_callback(const String\u0026amp; state) 25{ 26 if (state == \u0026#34;tap\u0026#34;) { b1_state = !b1_state; } 27 else if (state == \u0026#34;on\u0026#34;) { b1_state = true; } 28 else if (state == \u0026#34;off\u0026#34;) { b1_state = false; } 29 else if (state == \u0026#34;press\u0026#34;) { b1_state = true; } 30 else if (state == \u0026#34;pressup\u0026#34;) { b1_state = false; } 31 Button1.print(b1_state ? \u0026#34;on\u0026#34; : \u0026#34;off\u0026#34;); 32 digitalWrite(RELAYPIN_1, !b1_state); 33} 34 35void slider1_callback(int32_t value) { s1_value = value; } 36 37void heartbeat() 38{ 39 Button1.print(b1_state ? \u0026#34;on\u0026#34; : \u0026#34;off\u0026#34;); 40 Slider1.print(s1_value); 41 NUM1.print(100); 42 Text1.print(\u0026#34;Hello Blinker\u0026#34;); 43 Blinker.notify(\u0026#34;页面数据已刷新\u0026#34;); 44} 45 46void dataStorage() { Blinker.dataStorage(\u0026#34;data1\u0026#34;, random(0, 120)); } 47 48void setup() 49{ 50 Serial.begin(115200); 51 BLINKER_DEBUG.stream(Serial); 52 BLINKER_DEBUG.debugAll(); 53 54 pinMode(RELAYPIN_1, OUTPUT); 55 Blinker.begin(auth, ssid, pswd); 56 57 Button1.attach(button1_callback); 58 Slider1.attach(slider1_callback); 59 Blinker.attachDataStorage(dataStorage); 60 Blinker.attachHeartbeat(heartbeat); 61} 62 63void loop() { Blinker.run(); } 总结 Blinker 以其简洁的 API、丰富的组件和跨平台支持，极大降低了物联网开发门槛。掌握以下核心要点即可快速上手：\n宏定义在前，头文件在后 — 通信模式决定编译路径 组件声明在回调前 — 否则绑定失败 print 不放 loop() — 否则触发 MQTT 限流 滑块回调不 print — 否则拖不动 按钮保留五种状态 — 防止 APP 模式不匹配 WiFi 掉线要检测 — 三分钟无响应自动重启 遵循这些规范，你就能用 Blinker 快速搭建出稳定可靠的物联网应用。\n","permalink":"https://sukkk-zcy.github.io/posts/blinker-getting-started/","summary":"Blinker开发规范，适合嵌入式开发者快速上手","title":"Blinker 开发入门指南"},{"content":"前言 在上一篇 Blinker 开发入门指南 中，我们了解了 Blinker 的基础用法。但在实际项目中，WiFi 名称和密码写死在代码里非常不方便——每换一个网络就要重新烧录。\n本文介绍一个 ESP32 Web 配网方案，通过手机连接 ESP32 热点，在网页上配置 WiFi 和 Blinker 授权码，配置保存后开机自动重连。\n功能特性 开机自动连接已保存的 WiFi 连接失败时自动进入配网模式（AP 热点 + Web 服务器） Web 页面支持 WiFi 扫描选择、密码输入、Blinker 授权码填写 历史配置记录（最多3条），点击快速填充 GPIO0 长按3秒清除配置并重启 LED 指示灯状态：常亮=已连接，闪烁=配网中，熄灭=断开 项目结构 ESP32_Blinker_NET/ ├── src/ │ ├── main.cpp # 主程序：Blinker 初始化 + WiFi 管理 │ └── WIFIConnect.cpp # WiFi 配网模块实现 ├── include/ │ └── WIFIConnect.h # WiFi 配网模块头文件 ├── lib/ │ └── blinker-library-master/ # 本地 Blinker 库 └── platformio.ini # PlatformIO 配置 工作流程 flowchart TD A[开机] --\u0026gt; B{有已保存配置?} B --\u0026gt;|是| C[尝试连接WiFi] B --\u0026gt;|否| D[进入配网模式] C --\u0026gt;|成功| E[初始化Blinker] C --\u0026gt;|失败 20秒超时| D D --\u0026gt; F[ESP32_Config热点] F --\u0026gt; G[手机连接热点] G --\u0026gt; H[访问192.168.4.1] H --\u0026gt; I[Web页面配置] I --\u0026gt; J[保存配置] J --\u0026gt; K[ESP32重启] K --\u0026gt; C E --\u0026gt; L[正常运行] L --\u0026gt; M{WiFi断开?} M --\u0026gt;|是| N[自动重连] M --\u0026gt;|否| L N --\u0026gt;|重连失败3分钟| O[重启ESP32] 核心代码解析 主程序 main.cpp 主程序非常简洁，核心逻辑就三步：\n1#define BLINKER_PRINT Serial 2#define BLINKER_WIFI 3 4#include \u0026lt;Blinker.h\u0026gt; 5#include \u0026#34;WIFIConnect.h\u0026#34; 6 7WIFIConnect wifiManager(\u0026#34;ESP32_Config\u0026#34;); 8 9void setup() { 10 Serial.begin(115200); 11 wifiManager.begin(); 12 13 if (wifiManager.isConnected()) { 14 Blinker.begin( 15 wifiManager.getAuth().c_str(), 16 wifiManager.getSSID().c_str(), 17 \u0026#34;\u0026#34; 18 ); 19 Blinker.attachData(dataRead); 20 } 21} 22 23void loop() { 24 wifiManager.loop(); 25 if (wifiManager.isConnected()) { 26 Blinker.run(); 27 } 28} 关键点：\nwifiManager.begin() 内部处理了所有 WiFi 连接逻辑 Blinker.begin() 的密码参数传空字符串，因为 WiFi 已经通过 WiFi.begin() 连接 loop() 中先处理 WiFi 状态，再运行 Blinker WIFIConnect 类 这是配网的核心模块，封装了 AP 热点、Web 服务器、DNS 重定向、EEPROM 存储等功能。\n初始化流程：\n1void WIFIConnect::begin() { 2 pinMode(_ledPin, OUTPUT); 3 pinMode(_resetPin, INPUT_PULLUP); 4 5 if (restoreConfig()) { 6 // 有保存的配置 → 尝试连接 7 WiFi.begin(savedSSID, savedPassword); 8 // 等待20秒... 9 if (WiFi.status() == WL_CONNECTED) { 10 // 连接成功 11 } else { 12 setupMode(); // 进入配网模式 13 } 14 } else { 15 setupMode(); // 无配置，直接配网 16 } 17} 配网模式启动：\n1void WIFIConnect::setupMode() { 2 WiFi.mode(WIFI_AP); 3 WiFi.softAP(_apName); // 创建热点 \u0026#34;ESP32_Config\u0026#34; 4 dnsServer.start(53, \u0026#34;*\u0026#34;, IPAddress(192, 168, 4, 1)); // DNS重定向 5 server.on(\u0026#34;/\u0026#34;, HTTP_GET, handleRootCallback); 6 server.on(\u0026#34;/save\u0026#34;, HTTP_POST, handleSaveCallback); 7 server.begin(); 8 scanWiFi(); // 扫描可用WiFi 9} 配置保存：\n1void WIFIConnect::handleSave() { 2 String ssid = server.arg(\u0026#34;ssid\u0026#34;); 3 String password = server.arg(\u0026#34;password\u0026#34;); 4 String auth = server.arg(\u0026#34;auth\u0026#34;); 5 6 preferences.putString(\u0026#34;ssid\u0026#34;, ssid); 7 preferences.putString(\u0026#34;password\u0026#34;, password); 8 preferences.putString(\u0026#34;auth\u0026#34;, auth); 9 saveWiFiHistory(ssid, password, auth); 10 11 delay(1000); 12 ESP.restart(); // 保存后重启 13} Web 配网页面 配网页面包含：\nWiFi 网络下拉选择（自动扫描） WiFi 密码输入（支持显示/隐藏切换） Blinker 授权码输入 历史配置列表（点击快速填充） 保存并连接按钮 页面使用渐变背景和圆角卡片设计，手机端体验友好。\nWiFi 掉线检测 1void WIFIConnect::loop() { 2 // 每5秒检查WiFi状态 3 if (millis() - lastWifiCheck \u0026gt; 5000) { 4 if (WiFi.status() != WL_CONNECTED \u0026amp;\u0026amp; !_isSettingMode) { 5 WiFi.begin(savedSSID, savedPassword); // 尝试重连 6 } 7 lastWifiCheck = millis(); 8 } 9 10 // 长按GPIO0超过3秒 → 清除配置重启 11 if (digitalRead(_resetPin) == LOW) { 12 if (millis() - _buttonPressStartTime \u0026gt; 3000) { 13 reset(); 14 } 15 } 16} 硬件连接 引脚 功能 说明 GPIO2 LED 指示灯 连接=常亮，配网=闪烁，断开=熄灭 GPIO0 重置按键 长按3秒清除配置并重启 使用步骤 烧录固件到 ESP32 手机连接热点 ESP32_Config 浏览器访问 192.168.4.1 选择 WiFi、输入密码和 Blinker 授权码 点击保存，ESP32 自动重启并连接 总结 这个 Web 配网方案解决了 Blinker 项目中 WiFi 配置固化的问题，通过 Web 页面和历史记录功能，让用户无需重新烧录即可切换网络。配合 Blinker 的心跳包和回调机制，可以快速搭建物联网应用。\n本项目完整代码见 E:\\Embedded\\库\\Blinker开发规范\\ESP32_Blinker_NET\n","permalink":"https://sukkk-zcy.github.io/posts/esp32-blinker-web-config/","summary":"通过 Web 页面为 ESP32 配置 WiFi 和 Blinker 授权码，支持历史配置记录，开机自动重连","title":"ESP32 Web 配网实战"},{"content":"说明 本固件基于 @大聪明的二手脑袋 开源的 CCAirDetector 进行修改，改动如下：\n修改默认上电界面为 page3\n调换了 page2 和 page3 的部分显示组件\nweb 配网界面新增和风天气认证参数填写，无需配置 Arduino 环境，直接烧录固件即可\n下载工具和 bin 文件 下载并解压以下文件：\nflash_download_tool.zip\nCCAIR.bin\n固件烧录 选择你的芯片型号\n选择 bin 文件路径，左侧复选框需要打勾，地址为 0\n选择 COM 端口，点击 START，等待显示 FINISH 即烧录成功\n单击 PCB 上的 RST 以重启系统\n","permalink":"https://sukkk-zcy.github.io/posts/cc-air-monitor/","summary":"基于CCAirDetector修改的CC AIR MONITOR固件烧录与配置教程","title":"CC AIR MONITOR 固件烧录指南"},{"content":"引言 因网络原因，PlatformIO首次创建项目时容易出些一些问题，目前网络教程大多是切换网络、镜像源、配置Python环境等，此类教程大都比较繁琐，且不一定能解决问题。\n本文以VS Code为例，提供首次安装和创建项目时无法加载的解决方案，其它IDE也可自行尝试。\n扩展启动失败 切换网络，使用热点/代理。 使用离线安装包。 点击下载PlatformIO离线安装包 首次创建项目无法加载 打开任务管理器将关于vscode/platformIO/python的进程全部关掉，不会关可以直接关机重启。 根据你的电脑路径，找到.platfromio文件夹将其删除。 下载中的.platformio解压在原文件夹路径，重新打开vscode，可能还有其他依赖加载，一般几十秒就可以正常启动了。 点击下载.platformio文件\n","permalink":"https://sukkk-zcy.github.io/posts/platformio-notes/","summary":"PlatformIO首次安装使用时遇到的问题及解决方案","title":"PlatformIO首次安装使用注意事项"},{"content":"前言 QSextend 是一个 Chrome 浏览器扩展合集，包含 5 款针对特定场景开发的实用工具，涵盖自动化操作、快捷键增强、倒计时提醒、监控检测和界面优化。\n下载地址： QSextend-v1.4.zip\n安装方式 解压下载的压缩包 打开 Chrome 浏览器，访问 chrome://extensions/ 开启右上角「开发者模式」 点击「加载已解压的扩展程序」，选择对应扩展文件夹 扩展一览 扩展 版本 说明 AutoMouseRelease v1.3 自动任务领取，三坐标循环点击抢单 ClockPro v1.3 多功能倒计时提醒，进度条预警 + 自动提交 SandMonitor v1.4 沙子关键词监控，检测关键词组合并提醒记录 SpeedKey v1.3 答题快捷键增强，A/S自动回车，D+数字快速选择 label-optimizer v1.0 做题界面优化助手，隐藏多余元素最大化图片展示空间 AutoMouseRelease v1.3 自动任务领取扩展，设置三个坐标点自动循环点击。\n核心功能：\n三坐标点击：查询、领取、确定，支持点选和手动输入 自动循环：按设定间隔和延迟执行点击序列 参数校验：查询间隔和领取延迟最低 300ms 毛玻璃 UI，平滑动画，折叠展开 面板可拖拽，位置自动保存 使用方式： 点击扩展图标注入控制面板，点击「点选」后点击页面对应位置设置坐标，调整参数后点击「开始」。\nClockPro v1.3 倒计时提醒扩展，监听网络请求自动启动倒计时，支持进度条预警和自动提交。\n核心功能：\n监听特定请求自动启动倒计时 预警进度条：剩余指定秒数时顶部显示红色进度条 自动提交：剩余指定秒数时自动模拟回车 面板可拖拽、可缩放，配置通过 Chrome Sync 多设备同步 参数说明：\n参数 默认值 说明 总倒计时 60s 倒计时总时长 预警提醒秒数 10s 剩余多少秒触发预警 自动提交触发秒数 5s 剩余多少秒自动按回车 SandMonitor v1.4 沙子关键词监控扩展，实时检测页面中的关键词组合并提醒记录。\n核心功能：\n实时监控页面文本中的 HW、ZZ、1688 关键词 触发规则：HW×2 / HW+ZZ / HW+1688 任意满足即弹窗 红色顶部横幅提醒，6 秒后自动消失 自动记录触发历史，支持按任务包分组、复制链接 F2 快捷键手动记录当前页面 SpeedKey v1.3 答题快捷键增强扩展，专为答题场景设计。\n核心功能：\nA 键：按下后延迟自动触发回车 S 键：按下后延迟自动触发回车（独立延迟） D+1~8：按下 D 键后按数字键，自动完成 D+数字+回车序列 每个功能支持独立开关和延迟调节（0~1000ms） label-optimizer v1.0 做题界面优化助手，用于优化做题界面布局，最大化图片和数据展示空间。\n核心功能：\n隐藏左侧菜单栏和顶部导航按钮 最大化商品图片显示区域 优化数据面板布局，压缩顶部 header 高度 悬浮按钮 / 扩展图标 / Ctrl+Shift+O 快捷键切换 技术栈 Chrome Extension Manifest V3 Content Script + Background Service Worker Chrome Storage API 纯 JavaScript，无外部依赖 许可证 仅供学习和个人使用。\n","permalink":"https://sukkk-zcy.github.io/posts/qsextend-overview/","summary":"5款实用Chrome扩展：自动抢单、倒计时提醒、沙子监控、答题快捷键、界面优化","title":"QSextend - Chrome浏览器扩展合集"},{"content":"教育背景 上海工程技术大学 硕士 电子电气工程学院 · 电子信息 河南工程学院 本科 计算机学院 · 物联网工程 研究方向 SDN / P4 IOT 技术栈 C/C++ Python ESP-IDF PlatformIO PCB Layout\n联系我 ","permalink":"https://sukkk-zcy.github.io/about/","summary":"about","title":"关于"}]