Nmon (Nigel's Monitor) 是AIX系统与Linux 系统上,开源免费的监控资源的工具。Njmon则是其下一代的形态。

Njmon的主要特点是:

  • 采用JSON保存数据。
  • 原生支持发送数据到InfluxDB。
  • 可配置不监控的数据。
  • 不支持终端显示数据。
  • 添加了相关工具,包括njmond、nmeasure、njmonchart等。

由于想玩玩InfluxDB,于是按照官方建议,部署了Njmon + InfluxDB + Grafana。然后总结以下缺点:

  • Njmon对Linux支持不足。比如Debian系统,需要自己编译。但是编译过程没什么困难。
  • Njmon虽然支持InfluxDB v2+,但Grafana上没找到能直接使用的模板。Grafana找到的模板是针对InfluxDB v1。
  • 相对Nmon,Njmon参考资料比较少。但是官方有提供Youtube视频教程。

Njmon + InfluxDB + Grafana的部署要点:

  1. njmon命令运行于“nimon”模式,直接连接到InfluxDB时,需要加参数-I
  2. njmon命令连接InfluxDB v2+,需要使用-O 组织名称 -T token这两个参数进行验证。

    # 示例,njmon连接到InfluxDB v2
    sudo ./njmon_Debian11_unknown_v80 -I -s 1 -i 127.0.0.1 -p 8086 -x bucket -O 组织名 -T xxxxxxxxxxx
    
  3. InfluxDB v2+同时提供v1接口时,需要创建对应数据库(Database)和保存策略(Retention Policies)。

    # 示例,InfluxDB v2创建对应的v1数据库和保存策略
    influx v1 dbrp create --db bucket-db --rp bucket-rp --bucket-id xxxxxx --default --org '组织名' --token 'xxxxxxxxxxx'
    
  4. InfluxDB可以使用Docker部署。

    # 示例,使用Docker部署InfluxDB v2.4.0
    docker run --name influxdb -p 8086:8086 -v /opt/influxdb/config.yml:/etc/influxdb2/config.yml --volume /opt/influxdb/data:/var/lib/influxdb2 influxdb:2.4.0
    
  5. Grafana如果没找到相关模板,只能找接近的,再自己修改一下。

总结。体验过InfluxDB + Grafana,挺爽的。只是,如果是用作监控,那么采集数据端不一定使用Njmon。

dstat是一个Linux监控工具。可定制采集数据,可设置采集频率,可输出字符界面和导出CSV。默认一秒一条监测数据。其中以top开头的参数,可以记录检测类型最大值的进程。比如--top-cpu记录CPU占用最大的命令,--top-cpu-adv还会记录CPU占用最大的进程ID等。参数--time的时间格式,需要通过环境变量DSTAT_TIMEFMT进行定义。

示例操作命令:

# 设置时间格式
export DSTAT_TIMEFMT='%Y-%m-%d %H:%M:%S'

# 执行监测,并导出CSV文件
dstat --time --cpu --mem --disk --io --net --sys --top-cpu-adv --top-mem --top-bio-adv --top-io-adv --output /opt/dstat_log/dstat_$(date +%Y%m%d).csv

使用时,可结合tmux,随时查看其采集数据,即时输出在终端。导出的CSV文件,需要下载到本地,并使用第三方工具生成图表。

在众多监控方案中,dstat不算优秀的解决方案,而且只有采集数据的功能。其记录数据,采用CSV格式。如果终端不够宽时,不能完整显示每行的采集数据。而且CSV格式不好扩展,比如--top-cpu-adv记录的数据,不适合机器理解。这里记录一下相关经验。

1 正式版的bug

安装过0.7.3和0.7.4两个版本,并使用Python3运行,都存在以下两个Bug。幸好是使用Python开发,可以直接修复。其安装路径为/usr/bin/dstat

a)在Debian 10以上使用Python3运行时,出现以下Bug:

/usr/bin/dstat:2619: DeprecationWarning: the imp module is deprecated in favour of importlib and slated for removal in Python 3.12; see the module's documentation for alternative uses
  import imp
Terminal width too small, trimming output.
Traceback (most recent call last):
  File "/usr/bin/dstat", line 2847, in <module>
    main()
  File "/usr/bin/dstat", line 2687, in main
    scheduler.run()
  File "/usr/lib/python3.10/sched.py", line 151, in run
    action(*argument, **kwargs)
  File "/usr/bin/dstat", line 2806, in perform
    oline = oline + o.showcsv() + o.showcsvend(totlist, vislist)
  File "/usr/bin/dstat", line 547, in showcsv
    if isinstance(self.val[name], types.ListType) or isinstance(self.val[name], types.TupleType):
NameError: name 'types' is not defined. Did you mean: 'type'?

解决办法,参考以下文档:

简单来说,改两行代码。如下:

# 第547行,改为:
if isinstance(self.val[name], (tuple, list)):

# 第552行,改为:
elif isinstance(self.val[name], str):

b)--top-mem参数统计错误的bug
参考文章:

修改方法def proc_splitline(filename, sep=None),改为:

if filename.startswith("/proc/") and filename.endswith("/stat") and filename != "/proc/stat":
    tmp = linecache.getline(filename, 1).split(sep)
    it = [i for i,c in enumerate(tmp) if c.endswith(')')]
    it = 2 if not it else it[-1]+1
    return tmp[0:1] + [' '.join(tmp[1:it])] + tmp[it:]
else:
    return linecache.getline(filename, 1).split(sep)

2 应用场景

感觉比较适合单机版,或者指定采集一些系统数据。不适合生产机大规模部署。如果非要用dstat不可,可以考虑 dstat + Fluentd + Influxdb + Grafana 这种组合方案。

最近因工作需要,研究了一下Linux服务器的监控方案,收获颇丰。

1 监控需求

服务器需要监控什么?可以分为硬件和软件,或者系统数据和业务数据。一般的监控解决方案,都是针对硬件、操作系统和常用软件(比如数据库、Docker之类)。涉及业务数据,需要二次开发。

2 监控方案设计

一般的监控方案,分为采集、存储、展示、告警,这四大模块或者功能。针对被监控服务器的数量,可灵活实施四个模块的部署方式。比较完善的整体解决方案,还包括“控制”模块,实现服务器集群的统一管理。

2.1 采集

数据采集程序,或者叫“探针”,一般是部署在被监控服务器上的程序。用于采集相关数据,要求占用系统资源小(主要是CPU、内存、磁盘、网络等),对系统影响小。

数据采集的方式,可以采用“推”(push)和“拉”(pull)模式。

“推”是数据采集程序主动把数据从采集端发到存储端。数据具有良好的实时性,方便内网部署并推送到外网服务器。但采集端太多,或者采集的数据比较大,需要考虑存储端的承受能力。遇到存储端没有收到数据时,不能确定是网络问题还是采集端问题。

“拉”是数据采集程序暴露出来,例如开放HTTP服务的端口,存储端去访问并获取数据。存储端也有更多的主动权,决定拉取频率,甚至决定采集样本,能降低带宽、减轻存储端的压力。采集不到数据时,能区分网络问题和采集端问题。如果对数据有实时需求,采集端需要保存未拉走的数据。

针对采集端有时只能部署在内网的情况,有的解决方案会提供“代理”或者“跳板”功能,实现采集端与存储断之间的数据连接。

2.2 存储

数据存储,就是把监控数据持久化,可以是文件(比如CSV文件),也可以是数据库(比如MySQL、InfluxDB)。目前主流的方案,基本采用时序数据库,例如InfluxDB。专门针对这种大量连续时间的数据,提供存储、查询、统计等功能。

2.3 展示

数据展示,一般是把监控数据生成图表,以便更直观地查看和分析。简单的方案是用Microsoft Excel之类的软件,根据导出CSV文件的数据,生成各种图表。主流和灵活的方案是使用可视化软件,例如Grafana,连接时序数据库并生成各种图表。Grafana能够实现实时展示和历史分析。

2.4 告警

通过检测采集的数据,发现超过指定危险指标时,向相关人员发送消息,就是告警。由于相关人员一般不会24小时盯着服务器,所以需要机器进行告警。InfluxDB、Prometheus、Grafana等都有告警功能。一般开源系统只提供邮件或Web Hook(调用钉钉接口)通知,商业系统(例如:阿里云的云监控)会有短信或电话通知。

3 解决方案

服务器监控的解决方案,像编程语言一样,没有一个万能方案,需要根据情况进行选择。这里列举一些相关软件或方案。

3.1 dstat

基于Python。默认一秒采集一条数据,数据定制性高。数据可显示在终端,也可导出CSV文件。需要使用第三方软件,例如Microsoft Excel之类,生成图表和分析数据。高级玩法是,搭配Fluentd,保存数据到InfluxDB,再用Grafana展示、分析。

优点是占用资源小,数据简单。缺点是由于使用CSV格式,复杂数据记录得不够好。而且当前版本(0.7.4)有bug,部署时需要自己修正。

3.2 njmon

C语言开发。nmon的升级版,可设置不收集的数据,数据格式采用JSON。不支持终端显示,原生支持导出文件和发送到InfluxDB。有官方工具处理保存的JSON文件。推荐的玩法是njmon + InfluxDB + Grafana。

优点是占用资源小,作者对整个监控方案考虑比较全面。缺点是对AIX较好但对Linux支持不足。比如Debian 11需要自己编译。Grafana的njmon模板大多针对AIX,而且仅有的Linux模板是针对InfluxDB v1,即使用InfluxQL而不是Flux。

3.3 glances

基于Python,开源跨平台,界面优秀。支持三种模式:单独运行、C/S、Web。提供XML-RPC服务、RESTful JSON接口,也可把数据保存到其它系统,包括InfluxDB。本身支持配置“Actions”,根据事件触发相应脚本,实现告警。

占用资源较大(包括CPU和磁盘空间),界面优秀,玩法多。适合桌面系统的监控。

3.4 Prometheus

基于Go,监控告警工具,使用“拉”模式采集数据。

3.5 Zabbix

企业级的开源的服务器监控管理系统,是完整的解决方案,基本可以替代阿里云的云监控之类的系统。Web控制台基于PHP,支持中文显示;采集端基于C,升级版改为Go;数据存储使用MySQL,未支持时序数据库是最突出的缺点。另外,告警功能不支持电话和短信通知。

非常适合企业内部管理服务器集群,便于运维人员使用。

3.6 阿里云的云监控

一般云主机的服务商都提供云监控功能,且基本监控免费,高级功能收费。阿里云的云监控,还能监控非阿里云的主机。使用这些云监控前,要确定是否可以把服务器监控数据发给云厂商,甚至安装云厂商的采集软件。

4 方案选型

根据不同的情况,总结一下各个方案的选型。

4.1 云主机

如果是购买云主机,可以考虑云服务提供商的云监控,一般免费提供基础监控功能,例如阿里云的云监控。但前提是,云主机可以安装云监控的采集端软件,并且接受相关数据上传到云服务提供商那边。另外,高级监控功能,需要付额使用。

4.2 运维管理的服务器集群

企业内部,有专门的运维人员管理服务器,针对硬件或操作系统相关数据的监控,则可以考虑Zabbix。

4.3 业务数据

node_exporter + Prometheus + Grafana,或者 Telegraf + InfluxDB + Grafana,这种方案适合收集业务日志。部署了InfluxDB,还能存储其它数据,个人觉得比较好玩。

近来项目遇到要显示SVG图像文件。本来SVG已不是新鲜事物,应该很好使用,现实并非如此。

设计说用AI(Adobe Illustrator)做出来的图,用Chrome打开会出现错误,比如图形位置不对、该显示的图形没显示等。用文本文件打开那图,会显示xml标签开头,而不是svg标签开头。

图片显示有误的问题,只能让设计去修正了。至于文件格式的问题,找到svgcleaner这个工具,转换一下就好了,体积还有所减少。虽然svgcleaner貌似不更新了,但工作正常。

以前一直用Inkscape代替曾经的Flash,设计矢量图,比如图标之类,还不错。不知道为什么大厂出品的AI会是这么多问题。

近来遇到关于库存与并发的问题。由于一直接触的系统都是没有考虑过商品库存的并发,加上解决过的并发问题,也只是简单直接地采用锁表的方式。所以导致踩坑。

问题1,商品基础数据与库存数量,设计在同一个表。

商品基础数据,包括库存数量,主要用于查询。但库存数量,还要解决经常变化,且可能出现并发的情况。如果简单使用锁,即使只锁一行数据,也会导致正在进行下单(涉及扣减库存)的商品不能被浏览(因为锁住,不能查询)。

为了减轻这个情况,下单时,检查库存数量是否足够购买时,不锁数据,等到保存订单数据,真正扣减库存时才加锁。本想着通过减少锁数据的时间,减少商品数据不能查询的情况。但是系统采用Java开发,使用了Spring + Hibernate框架。而Hibernate在事务内使用了一级缓存,即事务内未提交时,查询到的业务数据都放到一级缓存。事务内查询时,会先查询一级缓存,若命中,则不再查询数据库。就导致了检查库存时已获取了商品数据,扣减库存时(从一级缓存获取)不能获取到最新库存(特别是两个客户同时下单同一个商品的情况),最后在并发情况下扣减库存,就出现库存扣少1了的问题。

解决方案很简单,把商品基础数据与库存数据分开两个表存放。库存数据在扣减时,不影响商品浏览。

问题2,库存数量,需减少锁定时间。

由于客户浏览商品,或者添加商品到购物车,都需要查询库存数据。如果使用悲观锁,即锁表或锁数据后不能查询,会导致客户不能浏览。参考了以下文章,决定使用乐观锁,即不使用数据库锁。

超卖问题,高并发情况下,如何扣减库存
https://blog.csdn.net/u010347967/article/details/123788355

目前系统规模比较小,且没有涉及分布式,于是决定在扣减库存时直接更新数据的方式。即使用update语句扣减库存时,用where条件判断是否足够扣减,并返回是否扣减成功。

由于使用MySQL,update语句不能返回指定数据(但是,sql server可以使用update...output,PostgreSQL可用update...returning)。加上Hibernate不能同时执行update和select两个语句,最后采用存储过程。参考以下网址:

mysql update column then select updated value
https://stackoverflow.com/questions/24691576/mysql-update-column-then-select-updated-value

解决方案

总的来说,使用乐观锁(即没有使用数据库的锁),并利用MySQL存储过程实现扣减库存后返回结果。

1)库存表

create table `product_stock` (
  `productId` bigint not null comment '商品ID',
  `instock` int not null default '0' comment '库存数量',
  `createTime` datetime(3) default null comment '创建时间',
  `updateTime` datetime(3) default null comment '更新时间',
  primary key ( productId )
) engine=InnoDB default charset=utf8mb4 collate=utf8mb4_0900_ai_ci comment='商品库存';

2)扣减库存的存储过程

利用存储过程的out参数,返回扣减结果。当outUpdateQty返回的值大于零,扣减成功,否则失败。扣减成功,outStockAfter的值才是正确。

delimiter //
create procedure `product_reduce_instock`(
    in inProductId bigint, /*传入参数:商品ID*/
    in inReduceQty int, /*传入参数:扣减数量*/
    out outUpdateQty int, /*传出参数:实际扣减数量*/
    out outStockAfter int /*传出参数:更新后库存数量*/
)
begin
    -- 初始化返回的值
    set @updateQty=0;
    set @stockAfter=0;
    
    -- 执行扣减库存
    update product_stock 
    set instock = (@stockAfter := instock - (@updateQty := inReduceQty)), updateTime = now() 
    where productId = inProductId and instock >= inReduceQty;
    
    -- 传出参数赋值,即返回扣减结果
    set outUpdateStock=@updateQty;
    set outStockAfter=@updateQty;
end //

锐角云三角主机,入手两年多,一直在家作为私人服务器,稳定跑着各种服务。作为矿难出品,当年以299RMB入手,很多人说贵。但跑了这么久,现在感觉很超值。性价比高,秒杀各种ARM开发板。

先说缺点。当时由于是299,所以没有外壳和M.2接口的128GB SSD。为了不破坏自带的Win10,我另外买了个32GB SSD,装上Debian 10。30 多 RMB,包邮,型号是M.2 2242。CPU性能问题,不能胜任大型计算任务。试过编译minidlna,能跑到2.2Ghz,但仍然有点慢。

装系统时,注意要支持uefi32启动的。所以Debian 10的安装盘,只能用同时支持32位和64位的ISO文件。下载链接:
http://mirrors.163.com/debian-cd/10.2.0/multi-arch/iso-cd/

另外还有个硬伤,不能上电自动开机。如果家里电路不稳定,可能需要买个12V的UPS补救。另外,也可以参照以下教程,添加上电自动开机的功能:

N3450 锐角云硬改来电自启,加装3.5寸硬盘成功 https://hostloc.com/thread-713391-1-1.html

再来说优点吧。作为家用服务器,首先是省电。然后是8GB大内存(z8350那类电视棒一般是2GB或4GB)!CPU是N3450,x86架构,64位(服务器软件方面几乎没有障碍,例如直接跑Minecraft Bedrock),跑起来风扇不会经常转(就是噪音低)。接口丰富,千兆网卡、3个USB 3.0、HDMI 2.0、WiFi、蓝牙、SSD(M.2 2242)、耳机接口等。自带64GB存储,默认装Win10,就算不做服务器,作为高清播放器、办公电脑,也是可以。

原官网详细介绍(已不能访问)
https://www.acuteangle.com/product.html

更多相关资料,可以搜索关键词“锐角云 三角主机”。

本文记录了从jQurey转到原生JavaScript开发的相关处理。

一 历史

二十一世纪初,IE 6还在统治浏览器的时代,出现了一批JavaScript框架。除了提高前端开发效率,还屏蔽了各个浏览器的JavaScript接口差异。那时有3个产品印象比较深刻:

  1. prototype,http://prototypejs.org/
    其特点是在原生JavaScript基础上做扩展,定义通用的方法或接口,屏蔽各个浏览器的差异。很轻量,个人比较喜欢。
  2. Ext JS,https://www.sencha.com/products/extjs/
    数据与界面分离,提供丰富的UI组建,便于页面开发。当时浏览器JavaScript性能不高,用起来不够流畅,不适合简单排版布局的页面。但是对于开发一些管理系统,确实很方便。
  3. jQuery,https://jquery.com/
    最大特别是查找HTML元素很方便(前提是熟悉其搜索语法),有点函数式编程的味道。在那个需要手工修改HTML界面的年代,确实很方便。

二 当前

看看当前的浏览器,已经是Webkit内核的天下,加上IE已亡,ECMAScript 6普及……各个浏览器的JavaScript兼容性大大提高。所以,我们可以直接采用浏览器原生JavaScript,替代jQuery这类用于遍历或搜索DOM的框架。当然,复杂的界面,主要是响应式前端框架(AngularJS、React、VUE)的世界。

三 实现方法

主要参考这个文章,从jQuery转到原生JavaScript。

  • 你也许不需要 jQuery (You (Might) Don't Need jQuery)

https://github.com/nefe/You-Dont-Need-jQuery/blob/master/README.zh-CN.md

另外,对于页面上的异步请求(ajax),该文章没有提出timeout的处理。以下整理一个示例:

// 请求错误的类,用于传递错误信息
let RespError = class {
    constructor(code, msg, respJson) {
        this.code = code;
        this.msg = msg;
        this.respJson = respJson;
    }
};

// POST提交Json数据。调用ajaxJson方法前加上async就是同步调用,直接调用就是异步调用
// 默认超时10秒
let ajaxJson = async (url, formParam={}, onSuccess=(respJson)=>{}, 
        onFailed=(respError)=>{}, timeoutSec=10) => {
    let controller = new AbortController();
    let timeoutId = setTimeout(() => {
            // 超时后停止请求
            controller.abort();
            // 抛出超时的错误
            onFailed(new RespError(-1, 'TIMEOUT', null));
        }, timeoutSec * 1000);
    try {
       // 发起请求
        let response = await fetch(url, {
            signal: controller.signal, // 用于接收中断请求信号
            method: 'POST',
            cache: 'no-cache',
            headers: {
                // 声明请求的参数是JSON
                'Content-Type': 'application/json; charset=UTF-8'
            },
            body: JSON.stringify(param)
        });
        // 注意,响应的数据只能获取一次,包括response.json()和response.text()
        let respJson = await response.json();
        if(!response.ok) {
            // 请求失败,抛出自定义的错误对象
            throw new RespError(response.status, response.statusText, 
                respJson);
        }
        onSuccess(respJson);
    } catch(err) {
        onFailed(err instanceof RespError ? err 
            : new RespError(-1, err.message, null));
    } finally {
        // 请求结束,停止执行定时函数。避免相应成功后,抛出超时的错误。
        clearTimeout(timeoutId);
    }
};

关于Fetch的使用,参考:

  1. Fetch API 教程

https://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html

  1. mdn web docs - 使用 Fetch

https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch

用过Flutter,对数据的响应式,有进一步了解。于是,回过头来再看看VueJS,自然而然地理解了。

找到一个比较好的Vue3示例代码,展示了使用Vue3的主要代码,涉及Vue3的模板语言、组件等。

Vue3 起步简单示例
https://blog.csdn.net/thankseveryday/article/details/124741733

我在代码加上注释,更容易理解:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/vue@next"></script>
<title>Learn Vue3</title>
</head>

<body>
<!-- 界面模板 -->
<div id="app">
    <!-- 两个大括号,获取data()方法返回对象的属性的值,给文本绑定值 -->
    <p>{{counter}}</p>
    <p>
        <!-- :title是v-bind:title的缩写,给HTML标签的属性绑定值 -->
        <span :title="message">{{message}}</span>
    </p>
    
    <!-- @click 是 v-on:click 的缩写,指定事件的执行方法 -->
    <p><button @click="reverseMessage">翻转文字</button></p>
    
    <!-- “v-”前缀的特殊属性,是Vue的指令 -->
    <!-- 使用 v-model 指令来实现双向数据绑定 -->
    <p><input type="text" v-model="message"></p>
    
    <!-- v-if ,当值为true时,才显示对应HTML -->
    <p v-if="seen">你能看到我吗?</p>
    
    <p><button @click="seenYN">显示/隐藏</button></p>
    <h4>我爱吃的水果:</h4>
    <ul>
        <!-- v-for,循环输出HTML -->
        <li v-for="fruit in fruits">{{fruit}}</li>
    </ul>
    <h4>周末计划:</h4>
    <ol>
        <!-- todo-item对应自定义组件TodoItem -->
        <todo-item v-for="todo in todos" :todo="todo" :key="todo"></todo-item>
    </ol>
</div>
<script>
// 定义自定义组件
const TodoItem = {
    props: ['todo'],
    template: '<li>{{todo}}</li>'
}

// 数据对象,有点像Flutter的State或者Provider
const dataObj = {
    // 声明用到的自定义组件
    components: {
        TodoItem
    },
    
    // 定义数据对象,有用的属性可以先设置null,用于占位
    data() {
        console.log("vue3 demo, data()");
        return {
            counter: 0,
            message: "hello vue",
            seen: true,
            fruits: ["apple", "orange", "bananas"],
            todos: ["钢琴课", "绘画课", "看电影"]
        }
    },
    
    // 在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图
    created() {
        console.log("vue3 demo, created()");
    },
    
    // 在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
    mounted() {
        console.log("vue3 demo, mounted()");
        setInterval(() => {
            this.counter++
        }, 1000);
    },
    
    // 操作数据的方法,一般用于页面元素的事件处理
    methods: {
        reverseMessage: function () {
            // 操作数据,让Vue更新界面
            this.message = this.message.split('').reverse().join('');
        },
        seenYN: function () {
            this.seen = !this.seen;
        }
    }
}

// 数据与模板绑定
Vue.createApp(dataObj).mount('#app')
</script>
</body>
</html>

更详细的介绍和相关文档,可以访问Vue3的官网:
https://v3.cn.vuejs.org/guide/introduction.html#vue-js-%E6%98%AF%E4%BB%80%E4%B9%88

注:本文不是教程。纯粹以玩和分享的角度做记录。

现今时代,微波炉已经足够廉价,可以成为普通家用电器。微波炉也是个加热工具,所以一直想试试用来烘培咖啡豆,但不知如何操作。直到刷到这个视频(注意,up主是个居住在中国的日本人,应该是用翻译软件翻译出来的中文,所以看起来有点别扭):

微波炉也能烘焙咖啡豆妈?使用微波炉的咖啡烘焙方法
https://www.bilibili.com/video/BV15Z4y1t77z

一 所需器具

  1. 微波炉,只要带调火力的功能的即可。
  2. 外壁比较厚(0.5cm左右吧)的玻璃器具,可以观察豆子的颜色变化,也可以锁住热量。我是用两个玻璃饭盒,一大一小。小的反过来,可以被大的包住。
  3. 隔热手套一双。
  4. 计时器。可以用手机自带的计时器(秒表)。最好是两个,一个是记录操作时间,一个记录总的烘培时间。没有的话,总的烘培时间可以看手机的时间。
  5. 金属盘子。刚出炉的咖啡豆比较热,最好用金属盘子装着。
  6. 风扇。下豆后给豆子降温。比较专业的,应该是用“去银皮冷却盘散热器”。
  7. 电子秤。

二 操作方式

视频的操作是,微波炉(分5档,低火、中低火、中火、中高火、高火)开高火,每隔30秒拿出来摇晃几下。130g生豆,约7分钟后一爆,约10分钟后二爆,约12分钟下豆。

我第一次试,50g生豆,用玻璃茶壶(外壁比较薄)。开高火,第一个30秒还没到就黑掉几颗,赶紧转中高火。可是全程中高火,烘了14分钟也没有一爆,但豆子没有糊。估计是没能锁住热量。

后面一次,110g生豆,用两个玻璃饭盒(外壁比较厚)。先开中高火,约5分钟后转黄,再开高火,约9分钟后(时间有点忘记了)一爆,一爆过后下豆。效果比较好。但是由于两个饭盒盖得比较紧,拖慢了下豆时间,导致有点过烘。

就是,没有一次成功的经历,但可以总结一些经验。正如该视频下的有个留言,“小火脱水,中火转黄,大火发展”,烘豆一般都是这样操作的过程。

三 需要注意

  1. 烘焙时,最好在室外,或者厨房里开排气、抽油烟机。毕竟不是每个人都喜欢烘焙时的气味。特别要注意下豆时,最好是在室外进行。因为整个烘焙过程的烟都被困在玻璃饭盒里,一打开就爆发出来,气味非常浓烈。
  2. 下豆最好在室外进行,第二个原因是要处理银皮。有专业机器的话,可以无视这点。
  3. 烘焙成品不均匀。应该是没有在烘培过程中进行搅拌而导致的。
  4. 烘焙过程不能测温。一般温度计不能放进微波炉,拿出来摇晃又不能打开(避免导致豆子失温)。
  5. 根据微波炉的加热原理,会导致豆子从内部发热出来,跟传统的烘豆(从外部给豆子加热)不同。不知道是不是这个原因,豆子太少(例如50g)就不能达到一爆。

四 总结

总的来说,微波炉烘豆,值得玩玩。毕竟器具都很家常,整个过程能够观察咖啡生豆的颜色和气味变化,烘焙过程也不会有银皮乱飞的烦恼。

但不建议作为日常烘豆。不能搅拌、不能测温,基本就不能控制并稳定出品的风味。

今年受疫情影响,几乎所有芯片都涨价了。但是合宙ESP32C3-CORE却奇迹地以9.9RMB包邮,其搭配的Air101-LCD屏幕扩展板(0.96寸)也是9.9RMB包邮。甚是吸引,于是入手了一套,主板+屏幕。

注:以下操作,以基于Debian的Linux发行版为例。

一 概述

合宙ESP32C3-CORE简单总结如下:

  1. 采用乐鑫科技的ESP32-C3芯片,搭载RISC-V 32位单核处理器,支持2.4 GHz Wi-Fi 和Bluetooth 5 (LE)。
  2. 板载4MB闪存。
  3. USB Type-C接口,集成CH343(带TTL串口转USB)。新版好像改为USB直连了。

相关资料

二 MicroPython

由于合宙的Lua OS采用Lua语言,虽然官方在努力,但本人不熟悉,就选择了更好玩的MicroPython。

相关资料

三 刷机

MicroPython官方关于ESP32-C3的固件及刷机教程:
https://micropython.org/download/esp32c3/

1. 安装USB串口驱动

Windows,需要安装CH343的驱动。我使用Lubuntu 20.04,自动识别。另外,新版的合宙ESP32C3-CORE应该也不用装。

2. 刷机工具

安装Python 3.8或3.7后,再装刷机工具esptool。使用sudo安装,是方便所有用户都可以用。使用pip3是指定安装Python3的版本。

sudo pip3 install esptool

3. 下载固件

在MicroPython官方网站 https://micropython.org/download/esp32c3/ 底部的Firmware -> Releases,下载最新版本的固件。

4. 清除原固件

--port为端口,要根据实际填写,我电脑上的是/dev/ttyACM0

sudo esptool.py --chip esp32-c3 --port /dev/ttyACM0 erase_flash

5. 刷入固件

--port为端口,/opt/download/esp32c3-20220618-v1.19.1.bin为MicroPython固件文件。另外,如果刷入不成功,可以多刷几次。

sudo esptool.py --chip esp32-c3 --port /dev/ttyACM0 --baud 460800 write_flash -z 0x0 /opt/download/esp32c3-20220618-v1.19.1.bin

四 开发

推荐使用Thonny作为开发IDE。可以先不上传代码而直接运行,也可以看到开发板上的文件。

相关资料

先安装python3-tk

sudo apt install python3-tk

再安装thonny

sudo pip3 install thonny

运行

thonny

插上开发板,在Thonny进入 工具 -> 设置 -> 解释器 -> 选择解释器为“MicroPython (ESP32)”,然后就可以开发了。

五 点亮屏幕

Air101-LCD屏幕的使用有几点需要注意的:

  1. 不能使用HSPI(硬件SPI),只能使用软SPI,即SoftSPI
  2. 该屏颜色不对,因此需要定义函数来生成正确的颜色。
  3. 横屏时,即tft.rotation(1),x轴不偏移,y轴偏移24像素。相反,竖屏时,即不写tft.rotation(1),x轴偏移24像素,y轴不偏移。
  4. 屏幕的RKey应该接到ESP32C3-CORE的GPIO13,但不知道为什么不能读取点击事件,于是该为接在GPIO19。

相关资料

写了个示例代码显示一些信息(如下),保存为main.py,连同ST7735驱动文件ST7735.py、英文字体文件terminalfont.py

from machine import Pin, SoftSPI, SPI
from ST7735 import TFT
import time
from terminalfont import terminalfont
import network
import ubinascii

# 由于TFT屏的颜色有问题,因此需要重写一个函数修复一下
def TFTColor(r,g,b) :
  return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)

spi = SoftSPI(baudrate=1000000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3), miso=Pin(10))
tft=TFT(spi,6,10,7) #DC, Reset, CS
tft.initr()
tft.rgb(True)
tft.rotation(1) # 横屏显示

# 绘制背景色
tft.fill(TFTColor(0,0,0))

# 绘制方块
#tft.fillrect((0,24),(20,20),TFTColor(0,0,255))

# 显示文字
tft.text((0,24),'mac',tft.WHITE,terminalfont,2)

# 显示MAC
mac = ubinascii.hexlify(network.WLAN().config('mac')).decode()
tft.text((0,40),mac,tft.WHITE,terminalfont,2)

# 显示运行秒数
from machine import Timer
sec = 0
def showTime(t) :
    global sec
    sec += 1
    tft.fillrect((0,56),(160,20),TFTColor(255,255,255))
    tft.text((0,60),f'Run {sec} sec',tft.BLACK,terminalfont,2)

# 运行定时器
tim0 = Timer(0)
tim0.init(period=1000, mode=Timer.PERIODIC, callback=showTime)

# 把按键信息显示在屏幕的函数
def showDirect(t) :
    global tft
    tft.fillrect((0,76),(160,16),TFTColor(0,0,0))
    tft.text((0,78),str(t),tft.WHITE,terminalfont,2)

# 设置按键的接口
from machine import Pin
keyL = Pin(9, Pin.IN, Pin.PULL_UP)
keyU = Pin(8, Pin.IN, Pin.PULL_UP)
keyC = Pin(4, Pin.IN, Pin.PULL_UP)
keyD = Pin(5, Pin.IN, Pin.PULL_UP)
keyR = Pin(19, Pin.IN, Pin.PULL_DOWN)

keyL.irq(trigger=Pin.IRQ_FALLING, handler=showDirect)
keyU.irq(trigger=Pin.IRQ_FALLING, handler=showDirect)
keyC.irq(trigger=Pin.IRQ_FALLING, handler=showDirect)
keyD.irq(trigger=Pin.IRQ_FALLING, handler=showDirect)
keyR.irq(trigger=Pin.IRQ_RISING, handler=showDirect)

这是另一个程序,显示一个走动的方块:

from machine import Pin, SoftSPI, SPI
from ST7735 import TFT
import time

# 由于ftf屏的颜色有问题,因此需要重写一个函数修复一下
def TFTColor(r,g,b) :
  return ((b & 0xF8) << 8) | ((g & 0xFC) << 3) | (r >> 3)

spi = SoftSPI(baudrate=1000000, polarity=1, phase=0, sck=Pin(2), mosi=Pin(3), miso=Pin(10))
tft=TFT(spi,6,10,7) #DC, Reset, CS
tft.initr()
tft.rgb(True)
tft.rotation(1) #方向调整

# 绘制背景色
tft.fill(TFTColor(0,0,0))

w = 20
h = 20
max = 160
for i in range(0,max*4-1):
  x = i * 5 % max
  y = i * 5 // max * h + 24
  tft.fillrect((x,y),(w,h),TFTColor(255,255,255))
  ++i
  time.sleep(0.04)
  tft.fillrect((x,y),(w,h),TFTColor(0,0,0))

六 后续

显示优化的问题,仍未解决(如下)。后面应该会试试Arduino for ESP32-C3。

  1. 有个项目解决中文的显示的,但刷固件失败,放弃了。

支持中文显示的MicroPython固件 https://github.com/wangshujun-tj/mpy-Framebuf-boost

  1. 想使用LVGL显示更好的UI,但是编译失败,也放弃了。

Micropython + lvgl https://github.com/lvgl/lv_micropython