普通视图

发现新文章,点击刷新页面。
昨天以前Eson Wong's Blog

我为什么开发 AI 英语听力生成器:Im-Listening

作者 Eson Wong
2026年2月26日 22:00

我为什么开发 AI 英语听力生成器:Im-Listening

我一直在保持自学英语。在听力方面,我经常面临一个艰难的挑战:市面上的听力材料要么太简单(比如慢速英语),没有挑战就无法学到新的知识;要么太难(比如 CNN/BBC 原声),语速过快,生词太多,挫败感极强。

我尝试过市面上的各种听力 App 和网络上推荐的资源,但始终找不到既符合我兴趣又适合我当前水平的听力材料。

既然找不到合适自己的工具,那就自己写一个。于是,我决定自己动手,开发一个工具,利用生成式 AI,根据我的兴趣和水平,即时生成个性化的英语听力材料:**listening.esonwong.com**

为什么要开发 listening.esonwong.com

1. i + 1 学习体验

语言习得大师 Stephen Krashen 提出的 “输入假说” 认为,只有当输入材料的难度稍微高于学习者当前水平(即 i+1)时,习得才会发生。如果材料太难(i+10),就是无效噪音;如果太简单(i+0),则学不到新东西。

想找到适合自己的材料是个大难题。市面上的听力材料往往是往往难以满足个性化需求。而通过 AI 生成听力材料,可以根据我的水平和兴趣,动态调整难度,实现真正的 i+1 学习体验。

2. 即时学习

听力练习需要大量的输入材料。传统的听力资源往往是预先录制好的,无法满足即时需求。而通过 AI 生成听力材料,可以根据将要面对的场景和兴趣,即时生成相关的听力内容,大大提高学习欲望。

比如,奖近年关,我就用 listening.esonwong.com 生成了和同事讨论 Spring Festival 的听力材料,帮助熟悉对这个话题听力能力:Chinese New Year Plans - ImListening

3. Cost Efficiency (成本效益)

市面上的听力材料和订阅听力 App 往往需要持续的费用。listening.esonwong.com 可以 0 成本生成听力材料,极大的提高了我练听力的动力。

核心功能与体验

难度控制

listening.esonwong.com 允许你根据 Difficulty Level(难度等级)和 Length(长度)来控制生成的听力材料整体难度。

难度控制

自定义话题

你可以自由的让 AI 生成你感兴趣的话题。例如输入主题让 AI 根据该主题生成听力材料。

或者输入单词列表让 AI 生成包含这些单词的听力材料。

1
生成一段包含以下单词的对话:economy, inflation, investment, market, growth

或者直接输入对话来生成音频听力材料:

1
2
3
4
5
6
Li: Hi, how are you doing today?
Wang: I'm doing well, thanks! How about you?
Li: I'm great, thanks for asking. Have you heard about the new project at work?
Wang: Yes, I have. It sounds really exciting!
Li: Definitely! I think it's going to be a great opportunity for all of us.
Wang: I agree. I'm looking forward to getting started on it.

又或者把一篇新闻文章输入进去,让 AI 帮你生成听力材料:

1
2
3
播报下面的新闻:

......

播放交互

了提供便捷的听力练习体验,支持多种设备和控制方式, listening.esonwong.com 的播放界面设计简洁,支持多种平台的媒体控制按键。可以指定重复模式为单句重复、段落重复或者整篇重复,满足不同的练习需求。也可以指定媒体的上/下一首按键的行为,比如切换到下一句或者下一段,方便在练习过程中快速切换听力内容。

播放界面

并且支持在锁屏显示听力材料的字幕,方便在各种场景下练习听力。

有了上述功能,让我在开车时练习听力成为了可能:

开车听听力

未来展望

目前的 listening.esonwong.com 已经实现了核心的听力生成和播放功能,但仍然有很多可以改进和扩展的空间。

未来我可能会添加以下功能:

  • 听写模式
  • 回顾提醒
  • 播放列表
  • 自定义语音 API

如果你也是英语学习者,欢迎体验 Listening.esonwong.com 并给我反馈!

Playwright E2E 测试框架入门教程

作者 Eson Wong
2025年10月26日 15:14

Playwright

www.einktodo.com 是我为 E-ink Todo List 设备开发的网站,为了减少在它上面的测试工作的时间,我使用了 Playwright 来进行 E2E 测试。

Playwright 是一个由微软开发的 E2E 测试框架,它可以模拟用户在浏览器中的操作,比如点击、输入、滚动等等。Playwright 支持多种浏览器,包括 Chrome、Firefox、Safari 等等,而且它还支持多种编程语言,比如 Javascript、Python、C# 等等。

在这篇文章中,我将向你展示如何使用 Playwright 来进行 E2E 测试。我们将会使用 Typescript 来编写测试代码。

安装 Playwright

在项目根目录下,运行以下命令来在项目中初始化 Playwright:

1
2
3
4
5
# npm
npm init playwright@latest

# yarn
yarn create playwright

初始化完成后,Playwright 会在项目中生成一个 playwright.config.ts 文件,和一个测试示例文件 example.spec.ts

Playwright 配置

生成的 playwright.config.ts 文件用 testDir 字段来指定测试文件的目录,projects 字段来指定义 Playwright 中的 Project 概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { PlaywrightTestConfig } from "@playwright/test";

export default defineConfig({
// 测试文件目录
testDir: "tests",

// ...

// 项目配置
projects: [
// ...
],
});

第一个测试

初始化完成后,Playwright 会在测试文件目录中生成一个测试示例的测试文件 example.spec.ts,我们可以在这个文件中编写我们的第一个测试。

下面以 www.einktodo.com 的首页标题为例,来编写一个测试。

  1. @playwright/test中导入testexpect函数
  2. 使用test函数来定义一个测试
  3. 使用page.goto方法来打开网站
  4. 使用expecttoHaveTitle方法来判断页面的标题是否为E-ink Todo List
1
2
3
4
5
6
import { test, expect } from "@playwright/test";

test("E-ink Todo List 首页标题", async ({ page }) => {
await page.goto("https://www.einktodo.com");
await expect(page).toHaveTitle("E-ink Todo List");
});

运行测试

默认模式

运行所有的 Projects

1
2
3
4
5
# npm
npx playwright test

# yarn
yarn playwright test

运行一个 Project

1
2
3
4
5
# npm
npx playwright test --project=projectName

# yarn
yarn playwright test --project=projectName

UI 模式

1
2
3
4
5
# npm
npx playwright test --ui

# yarn
yarn playwright test --ui

Playwright 中的 Project

Playwright 中的 Project 概念用于组织多个可以并行运行的测试文件、Project 运行的依赖和运行结束的后置任务,以及 Project 的环境配置。

mindmap  root((Project))

Projects 们的依赖关系

保存和使用登陆状态

参考

如何使用 LlamaIndex 构建一个 RAG 检索系统

作者 Eson Wong
2025年10月12日 19:37
mindmap  ((LLamaIndex))    ((知识库))    ((搜索引擎))    ((Agent))    ((Workflow))    ((大语言模型))

LLamaIndex 是一个基于大语言模型的应用的开源框架,可以用于构建 RAG 检索系统和其他基于大语言模型的应用。

本文将从浅及深地介绍如何使用 LlamaIndex 实现一个简单的以向量检索和搜索引擎检索作为基础的 RAG 检索系统。

准备工作

安装 VSCode,准备 Python 环境

我们从 VSCode 官网下载并安装 VSCode。

在电脑上新建一个 llamaindex-rag 的文件夹,然后在 VSCode 中打开这个文件夹。

打开目录

接下来我们安装 VSCode 的 Python 官方插件。

安装 VSCode 的 Python 官方插件

然后我们在 VSCode 使用下面的按键组合打开 VSCode 命令面板:

  • Windows/Linux: Ctrl + Shift + P
  • Mac: Cmd + Shift + P

打开命令面板

在命令面板中输入 > Python: Create Environment, 回车后选择 Venv 创建一个 Python 虚拟环境。

创建 Python 虚拟环境

创建虚拟环境之后,我们可以在打开的目录下看到一个 venv 的文件夹,这个文件夹内包含了 Python 的虚拟环境。同时 VSCode 也会自动激活这个虚拟环境。

接入大语言模型

Ollama 本地运行 llama 3.1 模型

Ollama 下载并安装 Ollama。

下载 Ollama

用组合键:

  • Windows/Linux: Ctrl + `
  • Mac: Cmd + `

在 VSCode 中打开终端,输入下面的命令在 Ollama 中安装 llama 3.1 模型:

1
ollama run llama3.1

使用 LlamaIndex 接入 Ollama

安装 LlamaIndex

在 VSCode 中打开终端,输入下面的命令安装 llama_index.llms.ollama LLamaIndex 的 Ollama 模块:

1
pip install llama_index.llms.ollama

在 VSCode 中新建 rag.py 文件:

1
2
3
4
5
from llama_index.llms.ollama import Ollama

llm = Ollama(model="llama3.1:latest", request_timeout=120.0)
resp = llm.complete("Who are you?");
print(resp)

点击 VSCode 左上角的三角形的运行按钮,我们可以看到在终端中看到打印出来的模型的输出。

运行 rag.py

构建和接入知识库

准备知识库示例文件

创建知识库目录

llamaindex-rag目录下新建一个 knowledge 目录,然后在 knowledge 目录,用于存放我们要检索的原始知识库的文件。

然后在 knowledge 目录下新建一个 example.txt 文件,用于存放我们的知识库示例。

用下面的内容填充 example.txt 文件:

1
Eson 是一个博客作者. 他的博客链接是 https://blog.eonwong.com.

你也可以在 knowledge 目录下面放置其它.txt.docx.pdf等格式的文件。

使用 LlamaIndex 构建知识库

安装 llama_index.corellama_index.embeddings.huggingface 模块。

在命令面板中执行下面的命令:

1
pip install llama-index llama_index.core llama_index.embeddings.huggingface
  • llama_index.core 模块提供了 LLamaIndex 的核心功能
  • llama_index.embeddings.huggingface 模块提供了 HuggingFace 的 embeddings 模型的接入功能。

在 VSCode 中新建 knowledge.py 文件,首先导入 llama_index.corellama_index.embeddings.huggingface 模块:

1
2
from llama_index.core import ( SimpleDirectoryReader, Settings, VectorStoreIndex)
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

设置 HuggingFace 的 embeddings 模型,我们选择在 HuggingFace 网站上热门的 baai/bge-m3 作为 LLamaIndex 向量化数据的 embedding 模型,在 knowledge.py 文件中添加下面的代码:

1
2
# 配置嵌入模型
Settings.embed_model = HuggingFaceEmbedding(model_name="baai/bge-m3")

接下来我们使用 SimpleDirectoryReader 读取 knowledge 目录下的文件:

1
2
3
# 读取文件
reader = SimpleDirectoryReader(input_dir="./knowledge")
documents = reader.load_data()

然后我们使用 VectorStoreIndex 向量化我们的知识库:

1
2
# embedding 向量化
index = VectorStoreIndex.from_documents(documents)

向量化之后我们要把向量化的知识库保存到磁盘上,以便后续的检索:

1
2
# 保存向量化的知识库
index.storage_context.persist(persist_dir="./storage")

运行 knowledge.py 文件,我们可以看到在终端中看,该脚本会先下载 baai/bge-m3 模型。

运行 knowledge.py

下载完成之后就会开始向量化知识库,然后保存向量化的数据保存到 storage 目录。

向量化知识库

检索知识库

rag.py 文件顶部导入 StorageContextload_index_from_storage 模块:

1
from llama_index.core import (StorageContext, load_index_from_storage)

rag.py 文件内容替换为下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 导入模块
from llama_index.llms.ollama import Ollama
from llama_index.core import (StorageContext, load_index_from_storage, Settings)
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 配置 LLM 和 Embedding 模型
llm = Ollama(model="llama3.1:latest", request_timeout=120.0)
Settings.llm = Ollama(model="llama3.1", request_timeout=360.0)
Settings.embed_model = HuggingFaceEmbedding(model_name="baai/bge-m3")

# 读取知识库
storage_context = StorageContext.from_defaults(persist_dir="./storage")
index = load_index_from_storage(storage_context)

# 配制查询引擎
query_engine = index.as_query_engine(streaming=True)

# 查询
response = query_engine.query("Eson 的博客地址是什么?")
print("输出结果:")
print(response)

接入搜索引擎

为大语言模型输入提供检索结果

the-best-product-i-bought-in-2024

作者 Eson Wong
2025年3月27日 12:01

年度事购入的好产品推荐

为什么写,

以前追更的产品评测视频作者,如今大都由赞助商提供产品和费用,更恶劣的是有相当多的作者欺骗视频平台和观众,把视频标记为不含广告,所以他们的评测已经无法信任。

为了让中文网络上还能有真实的产品评测,我每年都会写一篇这个系列的文章。

下面是我 2024 年购买的亲身体验过的,并且经过了考验的最好用的商品。

拓竹 A1 3D 打印机

黑鲨无线充电宝

小米电动晾衣架

Phonics 自然拼读常见规则 - 辅音字母的发音规律

作者 Eson Wong
2024年9月9日 22:53

辅音字母的读音通常是辅音字母发音的辅音部份。但是存在非常多的例外情况。

字母常见组合发音
B/b/不发音
C/k/ 和 /s//tʃ/
D/d//dʒ/
F/f//v/
G/g/ 和 /dʒ//ŋ/
H/h//θ/ /ð/ /tʃ/ /ʃ/ /w/ /f/ 不发音
J/dʒ/
K/k/
L/l/元音化
M/m/
N/n//ŋ/
P/p//f/
Q/k//kw/
R/r//r/
S/s//z//ʃ/ /ʒer/ /ʃ(ə)n/ /ʒən/
T/t//ʃ/ /tʃ/ /tr/
V/v/
W/w/元音化不发音
X/ks//gz//z/
Y/j/ /aɪ/ /ɪ/
Z/z/

本文持续更新中…

音节的划分对于一个单词的读音至关重要,自然拼读的规律基本上是针对单个音节的。如果你不了解什么是音节请阅读我的另一篇博客:Phonics 自然拼读常见规则 - 音节和元音字母的发音规律

什么是辅音

辅音是什么见 辅音

辅音字母的常见发音规律

B 的发音规律

B 通常读作 /b/。

  1. B 在音结尾部的 mb 和 bt 组合中,B 通常不发音。
  • comb /kəʊm/
  • bomb /bɒm/
  • climb /klaɪm/
  • lamb /læm/
  • thumb /θʌm/
  • debt /det/
  • doubt /daʊt/
  • subtle /ˈsʌtl/
  • plumb /plʌm/

C 的发音规律

mindmap((C 的发音规律))  (字母发音)   /k/   /s/   /ʃ/   /tʃ/  (组合发音)    (sc)      /sk/      /s/    (ch)      /tʃ/      /k/    (ck)      /k/
  1. C 通常读作 /k/

  2. C 在音节中后面跟 E, I, Y 时,通常读作 /s/

    在英语中它被称为 Soft C

    • cent /sent/
    • city /ˈsɪti/
    • cycle /ˈsaɪkl/
    • cell /sel/
    • ice /aɪs/
    • city /ˈsɪti/

    有时候也会读作 /ʃ/,如

    • ocean /ˈəʊʃən/
    • social /ˈsəʊʃəl/
    • special /ˈspeʃəl/
  3. C 在音节中后面跟 A, O, U 时,通常读作 /k/

    在英语中它被称为 Hard C

    • cat /kæt/
    • car /kɑːr/
    • cot /kɒt/
    • corn /kɔːn/
    • cut /kʌt/
    • cup /kʌp/
  4. C 与 S 组合成 sc 时,与上面类似。sc 后面跟 A, O, U 时,通常读作 /sk/,在 sc 后面跟 E, I, Y 时,读作 /s/

    • scan /skæn/
    • scent /sent/
    • science /ˈsaɪəns/
    • scythe /saɪð/
    • scope /skəʊp/
    • sculpture /ˈskʌlptʃər/
  5. C 在音节中与 H 组合成 ch 时,通常读作 /tʃ/

    • chat /tʃæt/
    • check /tʃek/
    • achieve /əˈtʃiːv/
    • chocolate /ˈtʃɒklət/
    • chunk /tʃʌŋ/
  6. C 在音节中与 H 组合成的 ch 有时读作 /k/

    • character /ˈkærəktər/
    • chemistry /ˈkemɪstri/
    • anchor /ˈæŋkər/
    • ache /eɪk/
  7. C 在音节中与 K 组合成 ck 时,通常读作 /k/

    • back /bæk/
    • black /blæk/
    • clock /klɒk/
    • duck /dʌk/
    • luck /lʌk/
    • sock /sɒk/

D 的发音规律

  1. D 通常读作 /d/

  2. D 有时候会读作 /t/

    • advanced /ədˈvɑːnst/
  3. 但在音节中的 dg 组合常读作 /dʒ/

    • edge /edʒ/
    • judge /dʒʌdʒ/
    • bridge /brɪdʒ/
    • badge /bædʒ/
    • knowledge /ˈnɒlɪdʒ/

F 的发音规律

  1. F 通常读作 /f/。

    • fan /fæn/
    • feel /fiːl/
    • fish /fɪʃ/
    • food /fuːd/
    • fun /fʌn/
  2. 极少数情况下会读作 /v/

  • of /ɒv/

G 的发音规律

mindmap((G 的发音规律))  (字母发音)   /g/   /dʒ/  (组合发音)    (dg)      /dʒ/    (gn)      /n/    (ng)      /ŋ/    (gh)      /g/
  1. G 通常读作 /g/

    • gap /ɡæp/
    • get /ɡet/
    • gift /ɡɪft/
    • go /ɡəʊ/
    • gun /ɡʌn/
  2. G 在音节中后面跟 E, I, Y 时,有时读作 /dʒ/,这被称为 Soft G

    • age /eɪdʒ/
    • change /tʃeɪndʒ/
    • gym /dʒɪm/
    • giant /ˈdʒaɪənt/
    • imagine /ɪˈmædʒɪn/
    • general /ˈdʒenrəl/
    • gentle /ˈdʒentl/

    也有一些例外情况,称为 Hard G

    • get /ɡet/
    • give /ɡɪv/
  3. 音节中的 dg 组合常读作 /dʒ/

    • knowledge /ˈnɒlɪdʒ/
    • edge /edʒ/
    • judge /dʒʌdʒ/
    • bridge /brɪdʒ/
  4. ng 在音节中的组合通常读作 /ŋ/

    • bang /bæŋ/
    • length /leŋθ/
    • sing /sɪŋ/
    • song /sɒŋ/
    • hungry /ˈhʌŋɡri/
  5. G 在音节中与 N 组合成 gn 时,G 通常不发音

    这被称为 Silent G,字母 K 也有类似的情况。

    • gnaw /nɔː/
    • gnome /nəʊm/
    • gnat /næt/
    • gnu /nuː/
    • sign /saɪn/
  6. G 在音节中与 H 组合成的 gh 通常读作 /g/

    • ghost /ɡəʊst/
    • ghastly /ˈɡæstli/
    • ghetto /ˈɡetəʊ/

H 的发音规律

mindmap((H 的发音规律))  (字母发音)   /h/  (组合发音)    (th)      /θ/      /ð/    (ch)      /tʃ/      /k/    (gh)      /f/      不发音    (wh)      /w/      /h/    (ph)      /f/    (sh)      /ʃ/
  1. H 通常读作 /h/

    • hat /hæt/
    • help /help/
    • hit /hɪt/
    • hot /hɒt/
    • hut /hʌt/
  2. H 在音节中的与 T 组合成 th 通常读作 /θ/

    • thank /θæŋk/
    • theory /ˈθɪəri/
    • think /θɪŋk/
    • thought /θɔːt/
    • thumb /θʌm/
  3. 但上面的组合在定词、冠词、介词、连词或副词以及后面跟着 er 时,通常读作 /ð/

    • that /ðæt/
    • the /ðə/
    • this /ðɪs/
    • those /ðəʊz/
    • father /ˈfɑːðər/
  4. H 在音节中与 C 组合成 ch 通常读作 /tʃ/

    • chat /tʃæt/
    • check /tʃek/
    • achieve /əˈtʃiːv/
    • chocolate /ˈtʃɒklət/
    • chunk /tʃʌŋ/
  5. H 在音节中与 C 组合成的 ch 有时读作 /k/

    • character /ˈkærəktər/
    • chemistry /ˈkemɪstri/
    • anchor /ˈæŋkər/
    • ache /eɪk/
  6. H 在音节中与 S 组合成 sh 通常读作 /ʃ/

    • she /ʃiː/
    • ship /ʃɪp/
    • shoe /ʃuː/
    • shop /ʃɒp/
    • shut /ʃʌt/
  7. H 在音节中与 W 组合成 wh 通常读作 /w/

    • what /wɒt/
    • when /wen/
    • which /wɪtʃ/
    • why /waɪ/
  8. 在上面的组合中,如果后面跟着元音字母 O,通常读作 /h/

    • who /huː/
    • whole /həʊl/
    • whose /huːz/
    • whom /huːm/
  9. H 在音节中的组合 gh 通常读作 /f/

    • laugh /lɑːf/
    • enough /ɪˈnʌf/
    • rough /rʌf/
    • tough /tʌf/
    • cough /kɒf/
  10. H 在音节中的组合 gh 也可能不发音

    • height /haɪt/
    • high /haɪ/
    • though /ðəʊ/
  11. H 在音节中的组合 ph 通常读作 /f/

    • phase /feɪz/
    • phenomenon /fɪˈnɒmɪnən/
    • philosophy /fɪˈlɒsəfi/
    • physical /ˈfɪzɪkl/
    • photo /ˈfəʊtəʊ/
  12. H 在音节中处于 ex 后面组成 exh 时,H 通常不发音

    • exhaust /ɪɡˈzɔːst/
    • exhibit /ɪɡˈzɪbɪt/

J 的发音规律

  1. J 通常读作 /dʒ/

    • jam /dʒæm/
    • jet /dʒet/
    • jingle /ˈdʒɪŋɡl/
    • job /dʒɒb/
    • jump /dʒʌmp/

K 的发音规律

K 通常读作 /k/。

  1. K 在音节中与 N 组合成 kn 时,K 通常不发音

    这被称为 Silent K

    • knack /næk/
    • knee /niː/
    • knife /naɪf/
    • know /nəʊ/
    • knuckle /ˈnʌkl/

L 的发音规律

L 通常读作 /l/。

  1. 在音节中位于元音后面的 l 的发音也用 /l/表示,但是一种特殊的发音方式,称为模糊 L(Dark L), 类似于呕的发音。

    • all /ɔːl/
    • well /wel/
    • till /tɪl/
    • cold /kəʊld/
    • bull /bʊl/
  2. L 在音节中的处于元音 a、o、ou 和辅音 f、v、k、m 之间会产生元音化现象

    • talk /tɔːk/
    • behalf /bɪˈhɑːf/
    • folk /fəʊk/
    • walk /wɔːk/
    • should /ʃʊd/
    • would /wʊd/
    • calm /kɑːm/
    • palm /pɑːm/

M 的发音规律

M 通常读作 /m/。

N 的发音规律

mindmap((N 的发音规律))  (字母发音)   /n/  (组合发音)    (mn)      /m/    (ng)      /ŋ/    (nk)      /ŋ/

N 通常读作 /n/。

  1. N 在音节中与 M 组合成 mn 时,N 通常不发音

    • column /ˈkɒləm/
    • condemn /kənˈdem/
    • autumn /ˈɔːtəm/
    • hymn /hɪm/
  2. N 在音节中与 G 组合成 ng 时,N 通常读作 /ŋ/

    • bang /bæŋ/
    • length /leŋθ/
    • sing /sɪŋ/
    • song /sɒŋ/
    • hungry /ˈhʌŋɡri/
  3. N 在单词中后面跟着 K 时,N 通常发 /ŋ/ 的音

    • bank /bæŋk/
    • drink /drɪŋk/
    • donkey /ˈdɒŋki/
    • thunk /θʌŋk/

P 的发音规律

P 通常读作 /p/。

  1. P 在音节中与 H 组合成 ph 时,通常读作 /f/

    • phase /feɪz/
    • phenomenon /fɪˈnɒmɪnən/
    • philosophy /fɪˈlɒsəfi/
    • physical /ˈfɪzɪkl/
    • photo /ˈfəʊtəʊ/

Q 的发音规律

Q 通常读作 /k/。Q 通常与 U 一起使用,读作 /kw/

  • quarter /ˈkwɔːtər/
  • queen /kwiːn/
  • quick /kwɪk/
  • quote /kwəʊt/

R 的发音规律

R 通常读作 /r/。

  1. R 在音节中的组合 wr 通常读作 /r/

    • wrap /ræp/
    • wreck /rek/
    • wrist /rɪst/
    • wrong /rɒŋ/
    • wrinkle /ˈrɪŋkl/
  2. R 在音节中的组合 rh 通常读作 /r/

    • rhyme /raɪm/
    • rhythm /ˈrɪðəm/

S 的发音规律

S 通常读作 /s/。

mindmap  ((S 的发音规律))   (字母发音)      /s/      /z/   (组合发音)      (su)        /ʃ/      (sure)        /ʒər/      (sion)        /ʃən/        /ʒən/
  1. 有一些情况下,S 会发 /z/ 音

    • is /ɪz/
    • was /wɒz/
    • his /hɪz/
    • has /hæz/
  2. S 在音节中与 U 组合成 su 时并且是重读音节时,通常读作 /ʃ/

    • sure /ʃʊər/
    • sugar /ˈʃʊɡər/
    • assure /əˈʃʊər/
  3. sure 的组合通常读作 /ʒər/

    • measure /ˈmeʒər/
    • pleasure /ˈpleʒər/
    • treasure /ˈtreʒər/
  4. S 在音节中与 H 组合成 sh 时,通常读作 /ʃ/

    见于 H 的发音规律

  5. S 在音节中与 ion 组合成 sion 时,通常读作 /ʃ(ə)n/ 或 /ʒən/

    • vision /ˈvɪʒən/
    • decision /dɪˈsɪʒən/
    • version /ˈvɜːrʒən/
    • explosion /ɪkˈspləʊʒən/
    • confusion /kənˈfjuːʒən/

T 的发音规律

mindmap  ((T 的发音规律))   (字母发音)      /t/   (组合发音)      (th)        /θ/        /ð/      (tch)        /tʃ/      (tr)        /tr/      (ture)        /tʃ/      (tu)        /ʃ/
  1. T 通常读作 /t/。

  2. T 在音节中与 H 组合成 th 时,见于 H 的发音规律

    • thank /θæŋk/

    • theory /ˈθɪəri/

    • think /θɪŋk/

    • thought /θɔːt/

    • thumb /θʌm/

    • that /ðæt/

    • the /ðə/

    • this /ðɪs/

    • those /ðəʊz/

    • father /ˈfɑːðər/

  3. T 在音节中的 ia, io 前面的 t 通常读作 /ʃ/

    • partial /ˈpɑːʃəl/
    • nation /ˈneɪʃən/
    • situation /ˌsɪtjʊˈeɪʃən/
    • action /ˈækʃən/
    • question /ˈkwestʃən/
  4. T 在音节中与 U 组合成 ture 时,T 通常读作 /tʃ/

    • nature /ˈneɪtʃər/
    • picture /ˈpɪktʃər/
    • future /ˈfjuːtʃər/
    • situation /ˌsɪtʃuˈeɪʃən/
  5. T 在音节中与 U 组合成 tu 时,T 有时读作 /ʃ/

    • situation /ˌsɪtʃuˈeɪʃən/
  6. T 在音节中与 CH 组合成 tch 时,通常读作 /tʃ/

    • catch /kætʃ/
    • watch /wɒtʃ/
    • match /mætʃ/
    • stitch /stɪtʃ/
    • witch /wɪtʃ/
  7. T 在音节中与 R 组合成 tr 时,通常读作 /tr/, /t/ 在这里是清音

    • track /træk/
    • train /treɪn/
    • tree /triː/
    • try /traɪ/
    • true /truː/

V 的发音规律

V 通常读作 /v/。

W 的发音规律

W 通常读作 /w/。

mindmap  ((W 的发音规律))   (字母发音)      /w/   (组合发音)      (wh)        /w/      (wr)        /r/      (who)         W 不发音      (在元音字母后面)        半元音
  1. W 在音节中在元音后面会变成半元音(Semi-vowel)。

    • saw /sɔː/
    • nephew /ˈnɛfjuː/
    • cow /kaʊ/
    • now /naʊ/
    • low /ləʊ/
    • how /haʊ/
  2. W 在音节中与 H 组合成 wh 时 通常读作 /w/

    • what /wɒt/
    • when /wen/
    • which /wɪtʃ/
    • why /waɪ/
  3. W 在音节中的组合 who, W 通常不发音

    • who /huː/
    • whole /həʊl/
    • whose /huːz/
  4. W 在音节中的组合 wr 时常读作 /r/,W 不发音

    • wrap /ræp/
    • wreck /rek/
    • wrist /rɪst/
    • write /raɪt/
    • wrong /rɒŋ/
    • wrinkle /ˈrɪŋkl/

X 的发音规律

  1. X 在音节中的中间和结尾通常读作 /ks/。

    • box /bɒks/
    • six /sɪks/
    • mix /mɪks/
    • fox /fɒks/
    • tax /tæks/
  2. X 在单词中与 E 组合成 xe 时,X 通常读作 /gz/。

    • example /ɪɡˈzɑːmpl/
    • exam /ɪɡˈzæm/
    • exact /ɪɡˈzækt/
  3. X 在单词的起始位置通常读作 /z/。

    • xerox /ˈzɪərɒks/
    • xylophone /ˈzaɪləfəʊn/

Y 的发音规律

Y 可以作为元音字母,也可以作为辅音字母。作为元音字母时,Y 通常读作 /aɪ/ 或 /ɪ/,与 I 的发音规律类似。

作为辅音字母时,Y 通常读作 /j/。

  1. Y 作为元音字母时并在重音节中发 /aɪ/ 的音

    • my /maɪ/
    • by /baɪ/
    • fly /flaɪ/
    • sky /skaɪ/
    • cry /kraɪ/
    • try /traɪ/
  2. Y 作为元音字母时并在非重音节中发 /ɪ/ 的音

    • happy /ˈhæpi/
    • baby /ˈbeɪbi/
    • city /ˈsɪti/
    • family /ˈfæmɪli/
    • carry /ˈkæri/
  3. Y 作为辅音字母时的发音规律

    • yes /jes/
    • yellow /ˈjeləʊ/
    • young /jʌŋ/
    • year /jɪər/
    • you /juː/

Z 的发音规律

  1. Z 通常读作 /z/。

    • zoo /zuː/
    • zero /ˈzɪərəʊ/
    • zip /zɪp/
    • zone /zəʊn/
    • buzz /bʌz/

浅聊 esp8266

作者 Eson Wong
2024年6月3日 00:25

PCB

最近在做 E-ink Todo List 项目,用到了 ESP8266,所以就简单的聊一下 ESP8266。

E-ink Todo List 网站

ESP8266EX

ESP8266 是由乐鑫科技推出的一个芯片系列,ESP8266EX 是其中的一款。

ESP8266EX Pin Layout

ESP8266EX 提供了完整 TCP/IP 协议栈和 WiFi 功能,一个 10 位 ADC,支持 SPI、I2C、UART、PWM 等接口。是一个非常适合用于做一些需要无线联网且由电池供电的项目的芯片。

ESP-12F 模组

ESP-12F 模组

为了快速创造我想要的东西,我使用了安可信的 ESP-12F 模组。使用 SMD 封装方式。它集成了 ESP8266EX 芯片,WiFi 天线和外部闪存的模组。

SMD 是 Surface-Mounted Device(表面贴装设备)的缩写。起源于 1960 年代,最初由美国 IBM 公司进行技术研发,之后于 1980 年代后期渐趋成熟。它的特点是元件直接贴装在电路板的表面,而不需要通过电路板的孔进行焊接。这种封装方式使得元件可以更小,更轻,而且可以在电路板的两面进行贴装。SMD0805 和 SMD0603 是常见的 SMD 封装规格,0805 封装的尺寸是 0.08 英寸 x 0.05 英寸( 2.0mm x 1.25mm),0603 封装的尺寸是 0.06 英寸 x 0.03 英寸(1.6mm x 0.8mm)。

ESP-12F 是特殊尺寸的 SMD 封装。

ESP-12F Pin Layout

固件下载

项目中用了 USB 转 TTL 串口芯片 CH340C 通过下面的引脚来实现自动下载固件和进入运行模式。

  • ENABLE

    使能引脚,当使能引脚为高电平时,芯片或模块处于工作状态;当使能引脚为低电平时,芯片或模块处于待机或关断状态。

  • GPIO0(FLASH)

    用于控制 ESP8266EX 进入下载模式。当 GPIO0 为低电平时, 让 ENABLE 引脚上升沿(从低电平到高电平)时,ESP8266EX 将进入下载模式。当 ESP8266EX 处于运行模式时,GPIO0 引脚可作为普通的输入输出引脚使用。

  • GPIO1(TX)

    UART 通用串口通讯的接收引脚

  • GPIO3(RX)

    UART 通用串口通讯的接收引脚

我会单独写一篇文章详细介绍自动下载电路的原理。

SPI 电路

SPI(Serial Peripheral Interface) 是一种芯片与芯片的外围设备通信协议。需要用到 4 条线路,分别是:

  • SCK(Serial Clock)

    串行时钟信号,由主设备产生,用于对齐数据传输的间隔。

  • MOSI(Master Out Slave In)

    主设备输出,从设备输入,主设备发送数据到从设备。

  • MISO(Master In Slave Out)

    主设备输入,从设备输出,从设备发送数据到主设备。

  • SS(Slave Select) 或 CS(Chip Select)

    从设备选择信号,由主设备产生,用于选择从设备。

我在项目中使用 SPI 通信方式与 E-ink 屏幕通信。除了上面的 4 条线路外,还用要控制 E-ink 屏幕的复位,以及读取 E-ink 屏幕是否忙碌的状态。

我在 ESP-12F 上使用了下面的引脚:

ESP-12FE-ink 驱动电路功能
GPIO2RST控制屏幕复位的引脚,低电平复位屏幕
GPIO4DC控制引脚屏幕的数据和命令模式,高电平表示数据,低电平表示命令
GPIO5BUSY屏幕忙碌状态输出引脚,高电平表示屏幕忙碌,低电平表示屏幕空闲
GPIO13(MOSI)DINSPI 通信 MOSI 引脚
GPIO14(SCK)CLKSPI 通信 SCK 引脚
GPIO15(CS)CSSPI 片选引脚

ADC 引脚

ESP-12F 有一个 10 位 ADC,可以用来读取模拟量信号。我将会用这个引脚来读取电池电压。ADC0 引脚可读取 0-1V 的电压。需要使用一个电阻分压电路将电池电压分压到 0-1V 范围内再通过 ADC0 引脚读取。

编程平台

ESP8266EX 的开发环境有很多,我选择了 PlatformIO。一开始我是使用 Arduino IDE 来开发的,但是 Arduino IDE 的功能太过简单,不适合太复杂的项目。

我使用 VSCode 安装了 PlatformIO 插件,通过 PlatformIO 的 CLI 工具来编译和下载固件。

参考

广告

本文由我在制作 E-ink Todo List 墨水屏待办 DIY 的 PCB 板时总结的,我还写了一篇《墨水屏 Todo List 制作教程》

👋 E-ink Todo List 墨水屏待办 DIY 的 PCB 现已上架售卖 😇,此 PCB 板可代替教程中的开发板和屏幕转接板。爱好 DIY 的朋友们可以下单购买制作。

E-ink Todo List 墨水屏待办的成品正在起来的路上!!!

下面是 PCB 板的购买途径。

EsonWong 的微信小店

E-ink Todo List PCB

墨水屏 Todo List 制作教程

作者 Eson Wong
2024年3月9日 00:00

墨水屏 Todo List 制作教程

我在 Twitter 发布的一条 tweet,写了我使用了墨水屏制作了一个 Todo List,可以同步苹果提醒的全家待办列表。收到了很多推友的关注,所以我决定写一个教程,帮助大家制作一个墨水屏 Todo List。

同步服务由我开发的 einktodo.com 提供。

同步苹果提醒的全家待办列表

创造真的令人感到愉快 https://t.co/guVXon73n5 pic.twitter.com/Rb9Fg3fUP7

— Eson Wong (@eson000) January 15, 2024

更新记录

  1. 2024-04 支持了滴答清单的同步

对成品感兴趣吗?

我正在尝试将墨水屏 Todo List 制作成产品,如果你对这个产品感兴趣,可以在前往 einktodo.com 加入行等待邮件列表,你将会收到产品制作的最新进度信息。

如果你对 E-ink Todo List DIY 也有兴趣和有意购买 DIY 电路板、DIY 套件、3D 打印版原型机,欢迎加入交流微信群。添加 EsonWong_ 微信号,备注E-ink Todo List 加入。也可以关注我的 X(Twitter) @eson000

广告

下面开始制作墨水屏 Todo List 的教程。

必要材料和工具

材料

ESP32 开发板、墨水屏驱动板和微雪 7.5 寸墨水屏

  • ESP8266/ESP32 开发板
  • 24 pin 墨水屏驱动板
  • 微雪 7.5 寸 800*480 分辨率墨水屏

注意: 开源固件已经放弃对 ESP32 的支持。

微雪 7.5 寸墨水屏可以在网上找到拆机屏,也可以买新的。注意要买 800x480 分辨率的。

墨水屏驱动板在闲鱼搜索墨水屏驱动板,买 24 pin 的通用驱动板。

或者

  • 上文中售卖的 E-ink Todo List 墨水屏待办 DIY PCB
  • 微雪 7.5 寸 800*480 分辨率墨水屏

E-ink Todo List 墨水屏待办 DIY PCB 可以代替开发板和墨水屏驱动板。

工具

  • 电烙铁
  • 电脑

注:如果你购买了 E-ink Todo List 墨水屏待办 DIY 的 PCB,则不需要电烙铁来焊接。

制作步骤

1. 焊接转接板和 ESP8266 开发板

  • 将墨水屏驱动板按下面的表格对应的脚位连接到 ESP8266 开发板上。

    墨水屏驱动板ESP8266 开发板
    VCC3.3V
    GNDGND
    SDAPin 14
    SCKPin 13
    CSPin 15
    DCPin 4
    RSTPin 2
    BUSYPin 5
  • 将墨水屏的 FPC 排线连接到墨水屏驱动板。

    注意:排线的正反面。这个 FPC 排线端子的触点是在上面的,所以排线的触点朝上。我在第一次尝试点亮墨水屏的时候,就因为排线插反了,当时信心受到了极大的打击。

    墨水屏连接到墨水屏驱动板

2. 安装固件编译和刷写环境

下载 VSCode,并安装。

安装好 VSCode 后,打开 VSCode,点击左侧的扩展按钮,搜索 PlatformIO,点击安装。

安装 PlatformIO

3. 下载代码

然后从 E-ink Todo List 固件代码开源仓库 下载固件代码。

点击右上角的绿色按钮,选择Download ZIP。下载完成后解压得到固件代码文件夹。

下载固件代码

4. 编译和烧录固件

  1. 用 VSCode 打开固件代码文件夹。

    打开文件夹

  2. 选择固件代码文件夹, PlatformIO 插件会自动安装编译固件所需依赖。

    打开的代码

  3. 将开发板用 USB 线连接到电脑。

  4. 编译和烧录固件到开发板。

    在 VSCode 上点击蚂蚁头形状的PlatformIO菜单来打开 PlatformIO 的面板。

    • 如果你用的是 E-ink Todo List 墨水屏待办 DIY 的 PCB,选择 Einktodo-开头的环境下的 General -> Upload and Monitor
      具体选哪个根据你的屏幕的型号来选择。比如是微雪 7.5 寸 800x480 分辨率的屏幕,选择 E-inktodo-Waveshare-750-W800 -> General -> Upload and Monitor
    • 如果你使用的是 ESP8266 开发板,选择 esp8266 -> General -> Upload and Monitor

    编译和烧录固件

    等待烧录完成。

墨水屏 Todo List 设置

在 einktodo.com 获取 API Token

  1. 登录 einktodo.com

  2. API Key 页面 创建 API Key。

    创建 API Key

设置墨水屏 Todo List

固件编译和烧录完成,墨水屏上会提示你连接墨水屏的热点和热点密码,接下来要连接热点进行设置。

  1. 连接墨水屏 Todo List 的 Wi-Fi: E-ink Todo List AP,密码: einktodo.com

  2. 打开浏览器,进入墨水屏上显示的设置页面。

    墨水屏 Todo List 初始化屏幕

    设置页面

  3. 设置墨水屏要连接的 Wi-Fi 网络和要使用的 API Token。

    点击Configure WiFi按钮进入设置页面。然后选择你想要让默水屏连接的 Wi-Fi 或在 SSID 输入框里输入 Wi-Fi 名。在 Password 输入框里输入这个 Wi-Fi 的密码。在 API Key 输入框里输入刚刚在 einktodo.com 获取的 API Key。

    墨水屏 Todo List 设置页面

  4. 点击Save按钮。

    墨水屏 Todo List 会自动重启并尝试连接 Wi-Fi。如果没有开始连接,你可以按 Reset 按钮重启 ESP32 开发板来触发连接。注意保持 Wi-Fi 信号良好。

如果尝试连接 3 次没有成功,墨水屏将会重新打开 Wi-Fi 热点,你可以重新连接 Wi-Fi 进行设置。

如果你使用 E-ink Todo List 墨水屏待办 DIY 的 PCB,长按靠近 USB 接口的按键也可以重新进入设置页面。

使用 E-ink Todo 同步助手同步苹果提醒

E-ink Todo 同步助手 beta 版已经发布,可以在 TestFlight 下载 E-ink Todo 同步助手 下载。

使用快捷指令同步苹果提醒

推荐使用 E-ink Todo 同步助手替代快捷指令来同步苹果提醒。

同步的 Todo List 目前支持苹果的提醒事项应用。其它常用的待办应用将来会逐步集成到 einktodo.com

安装和配制快捷指令

  1. 点击下面的链接在 iPhone 上安装Sync Einktodo.com快捷指令。

    Sync Einktodo.com 快捷指令

    安装 Sync Einktodo.com 快捷指令

    在 Safari 中打开上面的链接,点击获取捷径

    安装 Sync Einktodo.com 快捷指令

    点击添加快捷指令

  2. 添加完之后,找到 Sync Einktodo.com 快捷指令,点击它上面的...按钮,进入编辑界面。

    Sync Einktodo.com 快捷指令编辑页面

  3. Sync Einktodo.com快捷指令中的文本块中输入刚刚在 einktodo.com 获取的 API Key。

  4. 完成保存快捷指令。

设置快捷指令自动化

在快捷指令自动化中,设置打开或关闭“提醒事项”应用时运行此快捷指令来同步待办。

  1. 打开快捷指令应用,点击自动化 Tab

    设置快捷指令自动化

  2. 点击+按钮创建自动化, 选择App,点击下一步

    创建自动化

  3. 设置为打开关闭 提醒事项应用时运行快捷指令

    App 那一项选择提醒事项,勾选打开关闭,并选择立即执行,然后点击下一步

    设置同步触发条件

  4. 选择Sync Einktodo.com快捷指令。

    选择 Sync Einktodo.com 快捷指令

    这样设置后,当你打开或关闭提醒事项应用时,快捷指令会自动同步待办到墨水屏 Todo List。

  5. 允许快捷指令访问提醒事项

    当你第一次触发这个自动化时,系统会提示你允许快捷指令将提醒事项应用的数据发送到 einktodo.com,点击始终允许

    允许快捷指令访问提醒事项

2022 年我买过的最好用的商品

作者 Eson Wong
2022年12月28日 08:00

你是否曾购买了一件看起来很棒的产品,但是却在家里吃灰。也有一些商品让你惊喜不已,让你感觉购买是物超所值。在这篇文章中,我将向你介绍我 2022 年购买的亲身体验过的,并且经过了考验的最好用的 5 件商品。

AirPods Pro 2

AirPods Pro 2

我的单次通勤时间是一个半小时,全程地铁,由于地铁的运行噪音,我需要一幅降噪耳机。我之前用了 3 年的 AirPods Pro 1 代由于耳机的异响,而且苹果转门针对 AirPods Pro 1 代的维修计划不履行,再加上苹果生态的绑定,我又买了 AirPods Pro 2。

使用体验上二代比一代稍有改进,我感知比较强的两点是:降噪有所提升,耳机仓有了磁铁更方便充电。

组装 MagSafe 磁吸充电器

组装磁吸充电器

这是淘宝上买的一个山寨 MagSafe 充电器和一个无名的铝合金支架的组合,用蓝凝胶粘起来的。放在公司的办公桌上用,充电非常顺手。于是又买了一套放在家里用。

USB Type-C 转磁吸接口

USB Type-C 转磁吸接口

这也是在淘宝购入的,本来想买一对给 iPad 用,避免 iPad 充电频繁拔插。使用了之后发现挺好用的,又买了三对,在 Macbook Pro 上和充电宝这些会反复插拔 USB Type-C 接口的地方都用上了。这个东西支持 USB Gen 2 10Gbps 的数据传输。

Aqara 窗帘伴侣

Aqara 窗帘伴侣

这种窗帘伴侣可以无痛将普通窗帘变成智能窗帘。我在家里两个卧室都安装了这个,并设置了晚上自动关窗帘和早上自动打开的定时任务,避免小区对面楼晚上开灯照射到卧室,又让早上起床变得更加轻松。另外它可以通过网关设备同时接入米家和 HomeKit。

P3 空调伴侣

P3 空调伴侣

智能家居设备中除了灯具、窗帘伴侣,空调伴侣也是一个感知比较强感知的设备。我在家里两个卧室安装了这款空调伴侣。客厅的空调用的是一个博联的红外射频遥控器控制,同时也用它控制电视和遥控的电风扇。这样就消灭了家里所有的遥控器。

这些产品是我今年认为买到的比较值且使用频率比较高的物件,也是我今年最值得推荐的产品,希望对您的剁手有帮助。

转变自我的密码:《Atomic Habits》读后感

作者 Eson Wong
2023年11月30日 18:24

掌控习惯

在我们的生活里,习惯就像是无声的指挥官,默默地引导着我们的行动和选择。在《Atomic Habits》这本书中,詹姆斯·克利尔(James Clear)用他洞见卓识的笔触,展现了如何通过习惯的力量来雕塑更好的自我。这本书不仅是养成好习惯的指南,更是一部自我改革的宝典,它教会我们将小变化累加成巨大的成果。

作者简介

作者詹姆斯·克利尔,一个以自己的转变故事激励人们追求强大个人习惯的灵魂导师。通过在个人发展领域中的不懈努力和深刻的思考,他不仅在书籍的篇章中分享了他的智慧,也在他的网站和各类讲座中,持续地影响着那些渴望改变的灵魂。

习惯为什么如此重要

书中明示,习惯是我们日常决策的结果,它们定义了我们的效能和最终能达到的高度。我们之所以被困在不满的迷宫,很大程度源于那些未经意识审视的自动行为。因为习惯一旦养成,在无意识的驱动下,就会反复执行而不易察觉。

培养习惯的三层追求

克利尔在书中提出,培养习惯不仅是为了达成目标,更是为了雕塑身份。那种“我是谁”的信念,影响着我们的选择和行为。比如我自身的经历,将发布 iOS 应用视为过程而非一次性目标,还有每天阅读 15 分钟的习惯,正是这种理念的反映。

习惯的四大定律

书里提到的四大定律,为我们提供了改变习惯的明确路径。

让期望的习惯变得更加看得见、难以忽视

我们的大脑趋向于自动反应日常环境中的线索。通过创建显眼的触发器,如备好健身包放在门口,我们就构筑了迈向健身房的第一步。类似方法可以用于任何我们想要建立的习惯。

让习惯变得更有吸引力

积极的感觉是维持习惯的关键因素。我们可以把习惯与自己喜爱的活动关联起来,比如在跑步机上听喜爱的播客,以此增强坚持的动力。

让习惯变得更简单

降低好习惯的入门门槛至关重要。比如要养成早起的习惯,可以先从调早闹钟五分钟做起。而同时,增加不良习惯的难度,就像删除手机上的社交媒体应用,以避免不必要的干扰。

让习惯变得更有满足感

适当的即时奖励可以巩固好习惯。每当完成一项任务后给予自己奖励,无论是一杯咖啡还是一个小时的电视时间,都能够加强我们的积极反馈回路。

行动指南

书籍最终落脚在如何实践。制定一个鲜明的计划,如何每天写博客、读书、运动,然后根据习惯的四大定律将计划付诸行动。重要的是,要让这些活动符合你的身份认同,让自己真正相信“我是一个写作者”,“我是一个爱读书的人”,“我是一个健身者”。

结语

《Atomic Habits》不仅是一本关于习惯的书,它是一张改变生活的地图。詹姆斯·克利尔回答了我们在寻求进步路上的那些困惑,他用科学的方法和真实的故事,指引我们向着更好的自己前进。现在,是时候把这些理念应用于实践,开始自我改革的旅程了!

怎么用 Github Actions 部署 Next.js 项目到服务器

作者 Eson Wong
2023年10月10日 06:42

怎么用 Github Actions 部署 Next.js 项目到服务器?

最近开发一个项目 Next.js,部署到 Vercel 上在国内访问速度和稳定性都不太好,所以想部署到自己的服务器上。经过实践后,分享一下怎么用 Github Actions 部署 Next.js 项目到 Linux 服务器上。

服务器环境配置

在服务器要安装 Node.js 和 PM2,然后配置 Nginx。

安装 Node.js

推荐使用 fnm 来安装 Node.js,以后可以方便的使用不同版本的 Node.js。

1
curl -fsSL https://fnm.vercel.app/install | bash

安装好后重新 SSH 连接到服务器终端,用 fnm 安装 Node.js。

1
fnm install 18

安装 PM2

我们使用 PM2 来管理 Node.js 进程。以便在服务器重启后自动启动项目。

用 npm 安装 PM2:

1
npm install pm2 -g

Nginx 配置

Node.js 项目默认运行在 3000 端口,我们使用 Nginx 来反向代理到 3000 端口。这样就可以使用 80 或 443 端口来访问项目。

在 nginx 配置文件夹 /etc/nginx/sites-available 中创建一个配置文件 example.com

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name example.com;

location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}

然后在 sites-enabled 文件夹中创建一个软链接。

1
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com

然后重新加载 Nginx 配置文件:

1
sudo nginx -s reload

生成 SSL 证书

安装 certbot 来生成证书并修改 Nginx 配置文件以使用证书。

生成证书:

1
sudo certbot  --nginx

命令执行后会提示输入邮箱,然后选择同意条款,最后选择要生成证书的域名,然后就会自动的生成证书并修改 Nginx 配置文件和重新加载。

代码拉取 SSH 密钥配置

  1. 生成 SSH 密钥

  2. 把公钥添加到 Github 仓库的 Deploy keys 中

    添加公钥

  3. 把私钥到放到服务器的用于发布项目的用户的 ~/.ssh/id_rsa 路径,以便 Github Actions 用 SSH 连接服务器拉取 Github 上的代码。

    1
    echo "private-key" >> ~/.ssh/id_rsa

Github Actions 配置

我们使用 appleboy/ssh-action@master 这个 Github Action 用 ssh 来连接服务器,然后执行命令来部署项目。为了安全起见,我们把服务器 host 和端口,还有 ssh 密钥都配置到 Github 仓库的 Secrets 中,这样就不会把服务器的信息暴露到配置文件中。

准备部署 SSH 密钥

  1. 生成 SSH 密钥对用于发布项目

  2. 把公钥添加到服务器的 ~/.ssh/authorized_keys 文件中

    1
    echo "public-key" >> ~/.ssh/authorized_keys
  3. 在 Github 仓库的 Settings -> Secrets -> Actions 中添加名为 key 的 secret,值为私钥。

    添加私钥

设置 host 和端口

同样在 Github 仓库的 Settings -> Secrets -> Actions 中添加 hostport

创建 Github Actions 配置文件

在仓库的根目录中创建文件夹 .github/workflows。在此文件夹中创建一个 .yml 后缀的文件。

在配置文件中指定main分支在 push 时触发 Github Actions,然后指定一个 job 名为 deploy,在这个 job 中使用 appleboy/ssh-action@master 这个 Github Action 来连接服务器,然后执行部署脚本。在 Github Actions 中可以使用 Secrets 中的变量,这样就不会把服务器的信息暴露到配制文件中。

部署脚本首先用 git clone 拉取代码, 然后安装依赖,最后使用 PM2 启动项目。最后用 pm2 save 保存项目配置,以便在服务器重启后自动启动项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.host }}
username: ${{ secrets.username }}
key: ${{ secrets.key }}
port: ${{ secrets.port }}
script: |
git clone ${{ github.repository }} /home/username/project
cd /home/username/project
npm install
pm2 start npm --name "project" -- start
pm2 save

此后每次 push 到 main 分支时,Github Actions 就会自动部署项目到服务器上。

参考

在 iPad 使用 VS Code 写代码

作者 Eson Wong
2023年8月30日 05:54

iPad 上的 VS Code

VS Code 推出了 tunnel 插件和服务,现在终于可以方便的在 iPad 的浏览器上打开 VS Code 了。

VS Code 可以在一台电脑上运行 VS Code Server ,并通过 Github 账号登录开启一个 tunnel 隧道生成一个网址,然后在其他设备上打开这个网址就可以使用 VS Code 了。

VS Code Server

在服务器上也可以使用 VS Code CLI 命令行工具来启动 VS Code Server。

1
code tunnel

网页上使用 VS Code 的体验相当完整。我使用的插件基本上都可以使用。本址端口也可以方便的转换成一个公网地址,可以在 iPad 上访问。对于前端开发来说,可以完全在 iPad 上完成开发。

唯一的问题是 tunnel 隧道的网络延迟会不稳定,影响开发体验。

ESC 键

我在 VS Code 上使用 Vim 插件,需要使用 ESC 键切换模式。但是在 iPad 大部份键盘上没有 ESC 键,command + . 可以代替。也可以修改修饰键映射来解决。

添加到主屏幕

在 Safari 的分享菜单中,可以添加到主屏幕,这样就可以像一个 App 一样使用了。也能得到全屏幕的体验。

full screen

2022 年我买过的最好用的商品

作者 Eson Wong
2022年12月28日 08:00

你是否曾购买了一件看起来很棒的产品,但是却在家里吃灰。也有一些商品让你惊喜不已,让你感觉购买是物超所值。在这篇文章中,我将向你介绍我 2022 年购买的亲身体验过的,并且经过了考验的最好用的 5 件商品。

AirPods Pro 2

AirPods Pro 2

我的单次通勤时间是一个半小时,全程地铁,由于地铁的运行噪音,我需要一幅降噪耳机。我之前用了 3 年的 AirPods Pro 1 代由于耳机的异响,而且苹果转门针对 AirPods Pro 1 代的维修计划不履行,再加上苹果生态的绑定,我又买了 AirPods Pro 2。

使用体验上二代比一代稍有改进,我感知比较强的两点是:降噪有所提升,耳机仓有了磁铁更方便充电。

组装 MagSafe 磁吸充电器

组装磁吸充电器

这是淘宝上买的一个山寨 MagSafe 充电器和一个无名的铝合金支架的组合,用蓝凝胶粘起来的。放在公司的办公桌上用,充电非常顺手。于是又买了一套放在家里用。

USB Type-C 转磁吸接口

USB Type-C 转磁吸接口

这也是在淘宝购入的,本来想买一对给 iPad 用,避免 iPad 充电频繁拔插。使用了之后发现挺好用的,又买了三对,在 Macbook Pro 上和充电宝这些会反复插拔 USB Type-C 接口的地方都用上了。这个东西支持 USB Gen 2 10Gbps 的数据传输。

Aqara 窗帘伴侣

Aqara 窗帘伴侣

这种窗帘伴侣可以无痛将普通窗帘变成智能窗帘。我在家里两个卧室都安装了这个,并设置了晚上自动关窗帘和早上自动打开的定时任务,避免小区对面楼晚上开灯照射到卧室,又让早上起床变得更加轻松。另外它可以通过网关设备同时接入米家和 HomeKit。

P3 空调伴侣

P3 空调伴侣

智能家居设备中除了灯具、窗帘伴侣,空调伴侣也是一个感知比较强感知的设备。我在家里两个卧室安装了这款空调伴侣。客厅的空调用的是一个博联的红外射频遥控器控制,同时也用它控制电视和遥控的电风扇。这样就消灭了家里所有的遥控器。

这些产品是我今年认为买到的比较值且使用频率比较高的物件,也是我今年最值得推荐的产品,希望对您的剁手有帮助。

见闻、评论和推荐 2022-11

作者 Eson Wong
2022年11月27日 11:35

经济机器是怎样运行的

封面图:《经济机器是怎样运行的》视频封面。

科学技术见闻和评论。分享优秀的文章、视频、软件、服务和摘录。

推荐

文章

  • React 2022

    react 2022

    这是我写的 React 2022 生态太介绍的博文,希望能给要做 web 开发的人一些帮助。

软件和服务

  • Shields IO 是一个免费可以生成简单数据的图片服务。

    比如,可以生成 Twitter follow 数的和图片,每次加载就会显示最新的数据:eson's twitter

    生成 npm 和 GitHub 数据的图片 GitHub Repo stars npm。我的我的博文 React 2022 的生态 中大量使用了这个服务生成的图片来方便的展示数据内容。

  • tldr 命令行帮助

    tldr 是一个命令行程序,用于查询其它命令行程序的使用方法

    tldr 使用截图

  • Markwhen

    Markwhen 是一个用文本来绘制甘特图的服务, 它还有 VScode 的插件。

    Markwhen

  • Talk to Books

    Google 实验室出品, 根据自然语意来搜索书籍(仅英文)。

    Talk to Books

视频

图片

  • VIM 编辑器键位图,来自 Reddit 的用户 electricRGB

    VIM 编辑器键位图

    我在 VSCode 里用了几年 vim 键位,对它的不常用操作还是不熟练,于是在这个帖子找到了这张高清图片做桌面壁纸。

游戏

  • 星际拓荒(Outer Wilds)
    它是一款真开放世界的硬科幻解密游戏,你要在一个恒星系中探索它的奇特行星解开一个关于宇宙宏大秘密。作为一个对科幻迷,玩过《星际拓荒》之后,它成为我心目中仅次于《塞尔达·荒野之息》之后排名第二名的游戏作品。

    Alt text

摘录

  • 拖延最大的坏处不是耽误,而是会使自己变得犹豫,甚至丧失信心。不管什么事,决定了,就立刻去做,这本身就能使人生气勃勃,保持一种主动和快乐的心情。

    来源:文铁生《暴有国的事》

  • 做产品最怕的不是做错需求,而是在称不上对也不那么错的需求中消耗了宝贵的时间,还让产品变得非常笨重。

    来源:2 年整,我的第一次 SaaS 创业结束了

  • 独立开发者最难的根本不是创造产品,像我每天都有新的想法,而且执行力很强,几天就能肝出来。难的是如何把一个产品持之以恒地运营下去。这里说的运营下去是指,在不完美时就拿出来,找到你的用户群,根据反馈不断修正。这个过程包括写文章,做宣传,社交媒体互动,是独立开发者很不擅长的一块。

    来源:Hal 的推文

心得

  • 如果你刷手机超过半小时,就是用段完整的时间处理了一些碎片化事情。
  • 好奇心就像一根火柴,用正确的角度尝试小心的划过火柴盒粗糙表面就可以点燃热情和希望。划的角度过大或太用力,就会把火柴折断。
  • 为了使家成为家,需要投入时间。有的人们舍不得把时问花在家中琐事上,早出晚归,在外面奋斗和享受,家就成了一个旅舍。

Husky 入门教程

作者 Eson Wong
2022年4月2日 20:00

Husky

Husky 是一个 NPM 包,用更好的使用 Git 钩子来管理项目。通常它被用于项目的代码提交前的各类规范的检查。

安装和配置

  1. 安装 Husky

    1
    npm install husky --save-dev
  2. 启用 Husky

    1
    npx husky install
  3. 在 package.json 中添加 prepare 钩子,用于在 npm install 之后自动启用 Husky

    1
    npm set-script prepare "husky install"

    package.json 文件将会添加一个 prepare script:

    1
    2
    3
    4
    5
    {
    "scripts": {
    "prepare": "husky install"
    }
    }

创建钩子

1
npx husky add .husky/pre-commit "npm run lint"

配合 lint-staged 使用

lint-staged 是一个在提交代码前,只检查提交的文件的工具。它可以配合 Husky 使用,只检查提交的文件,而不是整个项目。

  1. 在项目中安装 lint-stagedeslint(或其他你需要的规范检查工具)

    1
    npm install lint-staged eslint --save-dev
  2. package.json 中添加 lint-staged 配置

    1
    2
    3
    4
    5
    {
    "lint-staged": {
    "*.{js,jsx,ts,tsx}": "eslint --fix"
    }
    }
  3. 创建使用 lint-staged 的钩子

    1
    npx husky add .husky/pre-commit "npx lint-staged"

配合 commitlint 使用

commitlint 是一个用于检查提交信息是否符合规范的工具。它可以配合 Husky 使用,检查提交信息是否符合规范。

  1. 在项目中安装 @commitlint/cli@commitlint/config-conventional

    1
    npm install @commitlint/cli @commitlint/config-conventional --save-dev
  2. 在项目根目录创建 commitlint.config.js 文件

    1
    module.exports = { extends: ["@commitlint/config-conventional"] };
  3. 创建使用 commitlint 的钩子

    1
    npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

结语

Husky 是一个非常好用的工具,可以帮助我们更好的管理项目的代码提交规范。用来解决因个人编码风格差异引发的潜在冲突,提升团队协作效率。

怎么使用 React Context API ?

作者 Eson Wong
2022年3月20日 08:00

React context 是 React 提供的基本工具,用于在组件间共享状态。

本文介绍 React context 的基本用法。

React Context API

什么是 React Context?

React context 允许我们不对组件树的每层组件手动添加 props 来传递数据的情况下,在组件间共享数据。它能让我们更容易的跨组件共享状态。

什么时候使用 React Context?

当某些状态可能在任何组件中都会被使用时,React context 是非常有用的。

比如:

  • UI 主题状态(亮暗模式、用户定制的 UI 风格等)
  • 当前会话的用户数据(用户名、头像等)
  • 当前语言或区域

React context 会让项目里的任何组件都能轻松访问到这些状态。

React context 解决了什么问题?

React context 帮我们解决了 props drilling 问题。

Props drilling 是指状态以 props 的形式从 React 组件树中一直往下传递, 但中间层组件并不需要这些 props, 只是做一个向下转发,这种情况就叫做 props drilling。

下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function App() {
const [user, setUser] = useState({ name: 'Eson', age: 18 });

return (
<Header user={user}>
)
}

const Header = ({ user }) => {
return (
<>
<Menu user={user}>
</>
)
}

你可以看到,Header 组件中的 user 状态是通过 props drilling 来获取的。App 的直接子组件 Header 并不需要 user 状态,它只是做一个向下转发。

怎么使用 React context?

  1. 使用 React context API React.createContext 方法创建 context

    1
    2
    3
    const UserContext = React.createContext({ name: "Eson", age: 18 }); // 第一个参数是默认值

    export default UserContext;
  2. 使用 React context 的 Provider 组件包裹你的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import React, { useState } from "react";
    import UserContext from "userContext.js";

    const App = () => {
    const [user, setUser] = useState({ name: "Eson", age: 18 });

    return (
    <UserContext.Provider value={user}>
    <Header />
    </UserContext.Provider>
    );
    };
  3. 使用 React context 的 Consumer 组件获取 context 状态,提供给使用状态的组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React from "react";
    import UserContext from "userContext.js";

    const Header = () => {
    return <Menu />;
    };

    const Menu = () => {
    return (
    <UserContext.Consumer>
    {(user) => (
    <div>
    {user.name}
    <button>退出</button>
    </div>
    )}
    </UserContext.Consumer>
    );
    };

上面的示例代码中,被 UserContext.Provider 组件包裹的组件树才可以通过 UserContext.Consumer 组件来获取用户的状态。

Context hook

随着 React 带来了 Hooks,我们可以使用 useContext 来获取 context 状态。

`useContext`` 接收一个 context 对象,返回一个 context 的值。它使我们的组件更加简洁,并允许我们创建自己的自定义钩子。

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { useContext } from "react";
import UserContext from "userContext.js";

const Menu = () => {
const user = useContext(UserContext);

return (
<div>
{user.name}
<button>退出</button>
</div>
);
};

使用 React context 的注意事项

解决 props drilling 的问题,React context 可以解决,但是它不是一个好的选择。第一是因为这会让组件的复用性变差,第二是 context 状态如果是一个对象,更新的时候会导致所有使用这个 context 的组件重新渲染,即使仅使用了未更新的属性值。

我们也可以更好组合我们的组件来解决这个问题,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from "react";

const App = () => {
const [user, setUser] = useState({ name: "Eson", age: 18 });

return (
<Header>
<Menu user={user} />
</Header>
);
};

const Header = ({ children }) => {
return <header>{children}</header>;
};

const Menu = ({ user }) => {
return (
<div>
{user.name}
<button>退出</button>
</div>
);
};

见闻、评论和推荐 2022-03-14

作者 Eson Wong
2022年3月14日 08:00

M1 系列芯片

封面图:M1 系列芯片。

科学技术见闻和评论。分享优秀的文章、视频、软件、服务和人生感悟等。

新闻(评论)

推荐

文章

摘录

  • 有些看似不起眼的“小工具”或“小技巧”,实质上可以强烈影响到你的工作效率或开发理念,强到你的职业生涯甚至可以拿它当分界线,分为泾渭分明的两块:“学会 XXX 前” vs “学会 XXX 之后”。对我来说,“tmux”、“VIM”、“写好的单元测试”、“完全使用英文搜索技术问题”均属于此类。 ——— 来源

  • 国内现在已经完全不存在“测评”这种东西了。消费者能看到的所谓“测评”,其实就两种:
    一种是给品牌做推广的。简而言之收了厂商钱,被厂商塞了口球之后玩服从性交易;
    另一种是给自己做推广的。一般会声明自己没收钱,然后用一些根本经不起推敲的测试(即便用实验室报告),收割没脑子观众的崇拜。
    所以就目前而言,消费者想购买一款相对较为陌生的产品,唯一靠谱且快捷的方法,是问自己身边且信得过的朋友。
    如果恰好有朋友亲手用或者曾用过,你可以无脑听朋友的想法。
    如果很不幸,你身边信得过的朋友都没用过,最靠谱的方法就是:亲自买来,亲自试用,并承担此过程带来的成本。
    ——— 来源

  • 灵活的人让自己适应世界,不灵活的人坚持让世界适应自己。结果,一切进步都来自不灵活的人。——— 萧伯纳

小红花的 Netowrk RC 在线分享 - Eson Wong

作者 Eson Wong
2022年3月12日 08:00

首先大家好,我是 Eson,然后感谢小红花为我提供的这么一个分享的机会,今天我来分享的是我用业余时间开发的一个控制作网络遥控车的开源项目,叫做 Network RC。

我先做个简单的自我介绍,我做了九年的 web 前端开发,现在在广州天河一家智能硬件公司做技术 Leader。

这次分享的内容,分为以下五个部分。第一个部分,详细的介绍一下这个项目。第二部分给大家实际演示一下,实机操作分屏幕分享大家看看。然后第三个部分就介绍一下用做这个遥控车主要的是哪几个硬件组成,我最初一开始实现的目标是用尽可能低的成本去实现一个可以远程操作的遥控车。第四个部分的话,就介绍一下我做的这个软件突出特性。第五个部分的话,就挑一两位观众来试玩一下这个遥控车, Network RC 有一个分享给网友操控的功能。

Network RC 介绍

下面就开始介绍一下 Network RC,主要分为两部分:一部分是运行在车子的树莓派上面,它是用来控制这个车子硬件的;一部分是控制端,它是可以在浏览器上运行,不管是 PC 端还是移动端浏览器或者各个操作系统平台都可以运行。

在 PPT 这个左边第一个图是我开发的 RC 的一辆原型车,左边这个黑盒子里面是树梅派,蓝色的这一块就是电池,白色这是一个 4G 网卡。

第二个图是我在广州,在自己家用手柄去操控上海网友用 Network RC 制作的一辆遥控车。

第三个图是展示 Network RC 的手机界面,你可以用 PC 也可以用手机控制。

第四个图,这里是一个方向盘,这里可能太小看不清,反正是可以支持用方向游戏游戏给赛车游戏用的方向盘去控制的。

Network RC 它有这么几个特性。首先它是开源免费的,然后,他需要的硬件设备和安装过程也都是非常的简单。它还支持多摄像头,就是你可以用一个前置,一个后置摄像头,避免视野的盲区。然后还支持语音对讲和自定义通道,通道是控制硬件的信道。Network RC 不仅可以控制车的前进后退和方向,就是油门和舵机。还可以控制云台,云台它一船是由两个通道组成一个 X 轴、Y 轴。你还可以自定义更多的通道,用来控制那些机械设备。

最重要的特性就是它的延迟,不管是控制延迟还是图传的延迟。图传的延迟它可以做到百毫秒的级别。

然后软硬件兼容性。如果你们玩过那个遥控车,能够区分玩具遥控车跟 RC 遥控车的话就知道。遥控车的档位,从玩具到高端的遥控车,他其实它有很多种价位,Network RC 的兼容性就比较好。

控制端,我刚刚说了,它是基于浏览器的,所以它是跨平台的,具有非常好的兼容性。

演示

好,接下来我就演示一下我的遥控车,它现在在我们家阳台上,我给你们演示一下。

这是 Network RC 的控制端的界面。上面这一排,就是控制车那边的开关,这是控制端的麦克风,车端的麦克风,Network RC 是有语音对讲的功能的。

然后这个是编辑 UI 的开关,可以编辑我们的控制端的 UI,就像游戏的操控 UI 一样,可以去编剧去拖动它的位置和改变大小。客户端要适配各类尺寸和比例的屏幕,还可以添加更多通道的 UI,所以需要让用户自己去自由的编辑。

这里面的话是一些设置,这里面涉及到很多设置项,连接设置,基本的设置。这里就是刚刚我们编辑那些 UI,我们可以增加更多的 UI 去控制更多的硬件,这里还有一些摄像头、声音、分享。

分享控制就是用到这个功能去分享一个控制链接,打开就可以远程的完我的遥控车。

下面这些的话是可以调整它的油门的大小。如果你连接手柄的话,这个手柄开关就会自动打开,可以用手柄控制或者把它关掉,就可以禁用手柄。这个重力开关是可以用手机的陀螺仪来控制。这里列出了我们可以用哪些键盘去来控制这个遥控车。这是一个播放内置音频的一个按钮,这里面可以这里面有更多的音频。可以通过设置里面去自定义。

大致的介绍就到这里,然后演示一下它的移动。我用现在是用键盘在控制 wsad 是控制它的移动,可以看到延迟是非常小的。然后云台控制云台控制是 ikjl 这四个键,可以通过云台来控制摄像头的角度。这辆车的话还可以看到车的内部,内部有电池的电压的显示。未来这些电压什么的监控都会做到软件里面,以后还会慢慢的更新。那好,那就演示的部分就到这里。

制作材料

接下来就给大家介绍一下制作材料。主要的制作材料就是。一个是树莓派,然后是遥控车,然后是网络设设备。

首先我们来讲树莓派,被控端是运行在树莓派上,然后控制端的 web 服务也是运行在树莓派上的。树莓派在维基百科上的描述是为学习计算机编程教育而设计的只有信用卡大小的微型电脑,系统是基于 Linux。它有很多的 GPIO 接口,可以用来控制遥控车的硬件,像上面这一排 40 个针脚,其中大部分针脚都是可以通过 PWM 和电平信号控制硬件,也就是就是那种频率信号和高低电平信号来控制这个硬件。

树莓派还有一个更小的版本 zero。可以让遥控车的成本制作更低,个占用的内部空间也更小,现在我已经。支持 zero 2 w。

然后说一下什么样的遥控车的支持。首先玩具遥控车,有的没有舵机,舵机是用来控制转向角度的,没有转向舵机,他就改不了。然后没有电调的话,我们可以去某宝上去买来改装。所以只要只要你的车能装上三线的电调和三线的转向舵机,它就可以用来改装这个网络遥控车。

这个图就是转向舵机和电调的接线的方法,我们还可以就接近刚刚我演示过那个摄像头的云台。然后还可以加加装一些开关,这是 Network RC 内置的默认的硬件的接线图。Network RC 支持 27 路的硬件通道,还可以扩展它去远程控制其它的硬件。

网络设备,如果你想远程玩的,我们一定要是提供一个网络设备的。但是你如果再放在家里面,连自己家里路由器就可以。

如果远程玩的话,你可以用一个旧手机的热点。或者用 4G/5G 的移动路由器来让这个树莓派连接网络。然后我们就可以远程的去操控它。当然你也可以用一些工业级的树莓派的一些 4G 或者 5G 模块。但是这个成本一般都很高。

除了上面那些硬件,Network RC 的用户,网络遥控车爱好者们,或者说用户他们为了有更方便的去让大家能制作遥控车,都纷纷自己贡献出自己的技能。像这块拓展版就是我们我的一个叫做龙卷风的爱好者绘制的一个扩展版。有了这个板子之后,车子的连接只要直接插上接口,不需要很复杂的接线了。它可以让制作 Network RC 遥控车变得更简单。未来,我还会针对这个扩展版来扩展它测量电池电压的功能,让控制短能够随时的知道电池的电量。

还有这一块扩展版也是这个爱好者绘制的,它可以接上航模电池就可以给树梅派供电,还可以给树莓派扩展 usb 接口。扩展了 usb 接口,你又可以接更多的摄像头或者音频设备,或者网络网络设备。

这也是 Network RC 者爱好者绘制的一些配件,这个摄像头的云台。某宝上当然也有这种云台售卖,但是相比好得多,所以做的这两款的话体积比较,还比某宝上那种便宜的。某宝的那个质量和和精度都达不到我们想做的遥控车的一些要求。

软件特性

接下来给大家介绍一下 Network RC 最重要的一个软件特性,就是的视频的延迟。

远程遥控车对视频的延迟的要求就是比较高,我在设计整个 Network RC 的时候,把视频的延迟的优先级设定的比较高。右边这个图,是在 4G 网络下的延迟表现。我的电脑是联通的宽带,遥控车是用一个 4G 网卡连接的,网络的延迟在 216 毫秒。

视频传输,我在开发的过程当中是经过多次优化和改进的。我们接下来我们来一步一步的看图传方案的改进。

首先一开始的话,我是树梅派的摄像头采集到视频画面之后用树莓派的 H264 硬编码进行进行视频的逐帧编码,然后再通过 FRPS 中转服务器, 再传输到浏览器端,再通过 H264 逐帧解码绘制到 canvas 的。第一个版本的话,可能就基本满足了我个人的一个需求。

但是树它非常依赖中转的服务器,服务器的距离和带宽非常影响 Network RC 用户的使用。

所以我又对改进,就是重新开发了基于 WebRTC 的方案。WebRTC 是网页浏览器进行实时语音对话或者视频对话的 API。

它在 2011 年是由谷歌和摩托罗拉,还有 Opera 支持的支持下,被 W3C 纳入标准的。现在已经发展了十十多个年头了。我们现在大部分基于网页的视频聊天,不是大部分可以说是所有的基于网页的视频聊天。甚至有些基于网页的直播平台,都是用 WebRTC ,基于这个技术去支撑他们的那个业务的。它的好处是可以网络穿透的情况下,不依赖中转服务器,可以直接连接到两个终端, p2p 点对点的连接,可以在浏览器之间去实现这么一个东西。然后,我的实现,就是用树梅派,也是作为一个 WebRTC 的一个终端,然后浏览器一个终端,树莓派和浏览器能够建立点对点连接。

但是,最后他还是产生了一个问题,就是它的画面延迟大。为什么?这整个解决方案虽然是针对。针对视频会话来设计的,网络遥控车对这个视频的延迟要求比这个视频会议要求更高。WebRTC 的视频编码和解码,更多的是考虑画面的延迟和质量的平衡,延迟和质量的平衡。我在使用 WebRTC 这个版本的时候,就发现他的编码延迟和这个缓冲延迟,比 Websocket + H264 逐针编码的方案就要大一点,而且它不能 100% 去建立点对点的连接。

后来的话就有了这个 WebRTC 数据通道加逐帧视频传输的最终方案。

这个方案是怎么样的?首先他我我保留了 Websocket 的整套方案,然后把 WebRTC 的方案进行了改进。用 WebRTC 数据传输通道来传递这个 H264 逐帧编码数据,然后传给浏览器,同样是通过浏览器的 H264 解码再绘制在 cavans 上,放弃了那个使 HTML5 的 video 标签进行那个视频解码,因为 video 标签的缓冲在各个浏览器里面,这个缓冲的大小是没办法去优化的。最终的效果就是最大程度的兼容了网网络环境。优先使用低网络延迟方式,也就是说用如果 WebRTC 打通了的话,所有的视频数据都会去走点对点通信这条网络路径。然后是低延迟的视频编解码,无缓冲视频解码与播放,最终就是达到我现在希望的一个效果。

这个就是 Network RC 技术上可能比较复杂的一块了。但是在功能和特性上,除了这个还有其实很多我想讲的,但是这次分享的话。就。就不啰嗦那么多了。

然后讲一下这个软件两端的一些依赖,就主要用到的一些技术。不管是控制还是浏览器,但我们主要的开发语言都是 JS,控制端的是用 Node JS。还有 express 这个 web 服务器。 wrtc 这个 Note JS 的 WebRTC 库。还有这个比较知名的视频转码的工具 ffmpeg。还有,这个是用来做网络穿透的也是一个比较知名的工具了,FRP。

浏览器端,我是用 React。还有这里是一个叫做 Broadway 的一个库,是用来在用 JS 去对 H264 解码的一个库。最主要的技术就是这么几个。

试玩

接下来我们进行试玩,有没有报名的小伙伴。

然后那我这边就分享完了。

❌
❌