米米的博客

做了一点微小的工作

ESP8266 是一款非常小巧的物联网芯片,在烧录了 AT 固件后,可以通过串口传递指令,进行 Wi-Fi 连接和发起 HTTP 请求。

市面上有许多集成了 ESP8266 芯片的开发板,例如 ESP-01 或 ESP-01S。笔者使用的是 ESP-01,已经预先刷好了 AT 固件。

准备工作

要将 ESP-01 连接到电脑上,需要准备一个 USB 转 TTL 模块,其输出接口应包括 VCC,GND,TX 和 RX。值得注意的是,ESP8266 芯片的供电电压是 3.3V,如果模块输出的 VCC 是 USB 的 5V 电压,那么不能直接接上 ESP-01,否则会造成损坏。

如果没有现成的硬件,用一块 Arduino 单片机也可以代替,因为 Arduino 单片机上是有 USB 转 UART 芯片的。向 Arduino 烧录一个空程序(即 setuploop 函数都为空的程序),就可以利用单片机上的 TX 和 RX 接口与 ESP-01 进行串口通讯了。不过这时要注意供电问题,ESP-01 在连接网络时功耗会增加,直接用 Arduino Nano 的 3.3V 输出可能带不动,会导致 ESP-01 重启。

连接方式

ESP-01 的引脚定义如下图所示。

ESP-01的引脚定义

使用时,将 VCC 和 CH-PD 接 3.3V 高电平,GND 接地,TX 和 RX 则连接到 USB 转 TTL 的模块(或者 Arduino 单片机)的 RX 和 TX 上。

串口测试

连接好之后,ESP-01 开发板上的电源指示灯会亮起。在电脑上打开串口通讯软件,例如 Arduino IDE 自带的串口监视器。AT 固件默认的参数是:串口波特率为 115200,换行符为 NL 和 CR。

接下来,可以通过 AT+GMR 命令查看固件信息:

AT固件串口测试

其它常见的 AT 命令可以参考以下文章:
ESP8266_AT Wiki
ESP8266 - AT Command Reference

ESP-01 上电自检时也会向串口打印出信息,但在一般的串口监视器中会显示为乱码。原因是 ESP-01 用的晶振是 26MHz 而非 40MHz,其串口波特率为。这个波特率并不常见(Arduino IDE 中就没有),因此无法正确显示。

Wi-Fi 连接

首先将 ESP8266 重置,然后设置为 AP + Station 模式。

1
2
3
AT+RST
AT+CWMODE=3
AT+CWLAP

最后一个 AT+CWLAP 命令将搜索附近的 Wi-Fi 并显示出来。
随后,使用 AT+CWJAP 命令,指定 SSID 和密码用于连接 Wi-Fi:

1
AT+CWJAP="ssid","pwd"

ssidpwd 根据情况替换。如果连接成功,将返回

1
2
WIFI CONNECTED
WIFI GOT IP

接下来就可以发起请求了。我们以 World Time API 为例,这个网站可以根据客户端 IP 获得时间。

阅读全文 »

摄于庚子年六月十四

Seen here is the Norwich City Council’s first computer, being delivered to the City Treasurer’s Department in Bethel Street, Norwich in 1957. The City of Norwich, and its forward-thinking Treasurer, Mr A.J. Barnard, were pioneers in the application of computer technology to the work of UK local authorities and businesses. In 1953-4, Mr Barnard and his team began looking for an electronic system to handle its rates and payroll. They began discussions with Elliott Brothers of London in 1955, and the City Council ordered the first Elliott 405 computer from them in January 1956. It was delivered to City Hall in February 1957 and became operational in April 1957. The event was celebrated by a demonstration of the machine in front of the Lord Mayor of Norwich and the press on 3 April 1957. (Norfolk Record Office, ACC 2005/170)

Below is a picture of the new $5 Raspberry Pi Zero at the same location. The Raspberry Pi is a tiny and affordable computer, designed and built in the UK, that you can use to learn programming through fun, practical projects. I own 2 older models.

Wonders never cease.


本文转载自:UK Computing: Elliott 405 (1957) vs $5 Raspberry Pi Zero (2015) by Ben Ward • Findery
图片来源:
Norfolk Record Office
BlazePress — 58 years later.

2019 年 8 月,Hexo 的 NexT 主题正式加入 PJAX 功能。最初的 PR 一共包含 35 个 commit,约 600 行代码改动。不过这个数字有些夸张,其中约有 200 行是在适配 PJAX 过程中,发现一些插件对于 PJAX 不友好,顺手修改了。此后根据收到的用户反馈,又用了不下十个 PR,修复了 PJAX 中全部已知问题。

由于在同一时期,NexT 已有弃用 jQuery 的计划,因此没有采用广泛使用的 jquery-pjax,而是用了一个较为小众的库:moOx/pjax,并且根据需求修改了一些功能。
适配 PJAX 的要点总结来说有以下几条。

将 PJAX 刷新区域分离

对于一个网站中的全部网页,往往具有一些在每个页面中相同的部分,以及在每个页面中不同的部分。相同部分可能包括 <head> 中加载的 <script>,以及导航栏、页脚等组件。不同部分则是页面的正文部分等。PJAX 的刷新区域应当只包含这些不同的部分。因此,将页面中的内容进行分类非常重要。对于使用各种模版引擎生成的网站,这一操作往往并不复杂,并且还有些额外的好处,例如可以对相同的内容进行缓存,加快网站的生成时间。

选择器

一般 PJAX 会根据设定的选择器来确定刷新页面中的哪些区域。这时需要确保选择器的唯一性。例如,如果文章内容由 Markdown 渲染生成,那么可能会在各个 Heading 处产生带有 id 的元素。需要避免它们与 PJAX 刷新区域的 id 冲突,那么可以考虑使用 class 进行选择。

一致性

另一个问题是 PJAX 刷新过程中,页面状态的一致性。NexT 主题在适配 PJAX 时就遇到了这个问题。在侧边栏中有两个区域:文章目录和站点概览。与之对应的是两个 <button>,点击一个 <button> 便会显示其对应的区域,隐藏另一个;这一过程由 className 控制。但有一些页面,例如首页,是没有文章目录的。如果从一个文章页面通过 PJAX 刷新回到首页,就可能导致侧边栏中的两个区域都被隐藏,看上去没有内容,并且 <button> 的状态与侧栏中两个区域的状态不符合。解决方案有两种:

  • 在每次 PJAX 刷新后,根据情况「点击」其中一个按钮,确保侧边栏显示正确;
  • 将控制显示的 className 移动到刷新区域外,例如设置为 <body>className

第二种方案可以减少 DOM reflow,保证 PJAX 刷新时渲染一步到位。

重新加载脚本

一些常见的 PJAX 插件都自带了重新加载脚本的功能,但为了精确地控制,这一部分可以考虑自行实现。

页面中的脚本大致可以分为三类:

  1. 在每个页面中都存在,但只需要加载一次,重复加载反而有可能导致问题(例如音乐播放器,看板娘,背景动画等)
  2. 在每个页面中都存在,并且 PJAX 刷新时需要重新加载(例如访问量统计,FancyBox 等)
  3. 仅在部分页面中存在,不使用时没有必要加载(例如 MathJax,网站评论区等)

第一类脚本

对于第一类脚本,将其放在 PJAX 刷新区域之外即可,不需要进行任何其它的处理。这里额外提一下 addEventListener 的问题。一般的 PJAX 插件会给出 PJAX 刷新完成时的事件,需要正确的使用。而 DOMContentLoadedload 事件都只会触发一次。
此外,应当避免重复注册相同的 addEventListener。如果每个新页面都使用匿名的回调函数注册一个相同的事件,最后一个事件将触发所有的回调函数,场面一定非常壮观。要避免这个问题,可以:

  • 不使用匿名函数作为回调函数,而是将其封装,通过 function 进行声明,然后将其作为 addEventListener 的参数,这可以保证其只触发一次;
  • 或者在必要时使用 removeEventListener

第二类脚本

对于第二类脚本,需要在 PJAX 刷新完成后,重新执行。最简单的方法是复制这个脚本的属性和内容,然后将其移除,再通过 replaceChild 方法将复制插入到原来的位置。
为了使 PJAX 区分第一类和第二类脚本,避免错误地加载,NexT 使用的方法是为全部第二类脚本加上 data-pjax 属性。这样,重新加载脚本的代码如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const elements = document.querySelectorAll('script[data-pjax]');
elements.forEach(function(element) {
const code = element.text || element.textContent || element.innerHTML || '';
const script = document.createElement('script');
if (element.id) {
script.id = element.id;
}
if (element.className) {
script.className = element.className;
}
if (element.type) {
script.type = element.type;
}
if (element.src) {
script.src = element.src;
// Force synchronous loading of peripheral JS.
script.async = false;
}
if (element.dataset.pjax !== undefined) {
script.dataset.pjax = '';
}
if (code !== '') {
script.appendChild(document.createTextNode(code));
}
element.parentNode.replaceChild(script, element);
});

复制 type 是为了避免执行非 JS 脚本,例如 MathJax 的配置。复制 dataset.pjax 则是为了确保在多次的 PJAX 刷新中,script[data-pjax] 选择器始终有效。

第三类脚本

比较麻烦的是第三类脚本。对于那些可以重复进行初始化的插件而言,其「本体」只需要加载一次,此后的 PJAX 刷新过程只需要再次进行初始化。典型的例子是 Valine 和 MathJax。对于 Valine 而言,每个新页面中,重新执行 new Valine() 即可。而对于 MathJax,初始化也是类似的。无论如何,最佳的实现应当保证:

  • 如果用户通过 PJAX 浏览的页面中,都不包含数学公式,那么无需加载 MathJax,减少网络请求;
  • 如果用户浏览到了第一个包含数学公式的页面,那么需要加载 MathJax;
  • 在此后用户浏览的所有页面中,如果包含数学公式,那么只需要调用以下方法,重新进行渲染
    1
    2
    3
    4
    MathJax.startup.document.state(0);
    MathJax.typesetClear();
    MathJax.texReset();
    MathJax.typesetPromise();

当然,你也可以通过其它方式实现同样的效果。如果要省事的话,可以将第三类脚本按照第二类的方式进行处理。唯一的不同是需要将其放置在 PJAX 刷新区域之内,否则可能造成额外的网络请求和资源消耗。

总结

总而言之,PJAX 适配过程中最大的难点是重新加载脚本的问题。想要做到完美而没有任何疏忽和遗漏无疑是困难的。这依赖于大量的测试来检验系统的鲁棒性。

0%