Bhe's Blog

Bhe's Blog

前言

在文章 [1]和 文章 [2] 中, 分别介绍了 bootloader 和 qmk 核心功能的移植.

这篇文章会稍微深入一些, 介绍自己一步一步了解和熟悉 stm32f103 的过程, 摸索之后配置好了键盘更多功能.

这篇文章中, 将通过阅读相关 datasheet, 了解 stm32 的基础知识, 给固件加上更多功能:

  • 外置持久化存储 i2c eeprom
  • 基于 dma 和 pwm 的 RGB Matrix.

成功点亮RGB

源码仓库 https://github.com/hitsmaxft/qmk-kb-bheznz

Read more »

前言

连接效果

在前文中, 我介绍了移植野火开发板 stm32f103开发板的 dfu bootloader, 接下来要介绍的是如何迁移qmk到开发板上.
这篇文章会涉及修改 qmk 源码,添加 stm32f103 键盘, 配置测试小键盘的布局, 最后通过 stm32duino 的 dfu 模式刷入固件.
一同解释 qmk 的一些基础知识.
后面的文章逐步深入到 RGB 和存储等.

源码仓库 https://github.com/hitsmaxft/qmk-kb-bheznz , 最新的代码在我的qmk-firmware 中, 这一份是手工尽量同步.

Read more »

为啥有这篇文章

最近我业余时间给自己的键盘刷上了 qmk 固件, 其中主控代码和 RGB 跑起来了, 但是不太理解硬件原理, 于是想到买了几年一直吃灰的野火开发版, 正好可以用来了解stm32的开发细节。

这里根据开发板的资料,一步步搭建起了专用的 qmk 固件.

这是第一篇文章,讲讲给开发板加上 bootloader。

接下来的文章内容中, 将一步一步讲解给指南者开发板修改了编译 stm32duino-bootloader 用于实现用户态 DFU 功能。

Read more »

前言

gemini-cli 的基础功能已经完备, 我就计划着把包发布到 pypi 上去, 这样子公司那边开发的工具也可以直接引用, 避免和工作的代码产生直接交集.

python 官方提供了很完善的 github action 发布和自动生成 release 的教程, 但它是基于 pip 和 build 写的, 用 poetry 实现构建依赖管理的项目就需要不少改动了.

而我是直接从 github 官方的 python 发布 workflow 修改而来, 只实现了构建和发布, release 以后再完善了.

需要以下几个步骤

  • 配置 workflow 文件
  • 配置仓库的发布 密钥
  • 在包的发布页面授权github workflow
Read more »

OrbStack 提供了 Linux Machine 功能, 可以在 mac os 上得 WSL2 一样的体验.

由于众所周知的原因, 我需要在宿主机上开了 clash 作为代理工具. 在本地的 7890 端口上提供 http 代理, 但没有开全局代理. 只有 firefox 和 github clone 会进过 7890

但这也就导致了 OrbStack 里的 linux 默认是不经过代理.
这里我想要的效果是 linux machine 全走代理. 宿主机还是保持按需使用.

Read more »

前言

由于前阵子 MacBookPro 重新格式化了, 于是用 nix-darwinhome-manager 重新构建了自己的配置管理. 这里写一篇文章记录一些个人对 nix 的学习和总结

Read more »

前言

由 chatgpt 生成…

在当今的技术领域,配置管理变得越来越重要。对于开发者和技术爱好者来说,保持一致的开发环境和工具配置是至关重要的。然而,传统的配置文件管理方式可能会导致配置分散、混乱和难以维护的问题。

幸运的是,有一个强大的工具可以帮助我们解决这些问题,那就是home-manager。Home-manager是一个基于Nix的工具,旨在帮助用户统一管理他们的配置文件,并通过声明性的方式进行配置。

本文将介绍如何安装home-manager以及使用它来管理一些常见的配置文件,例如zsh、bash和vim。我们将探索如何使用home-manager的内置功能和插件来简化配置文件的管理,并介绍如何定制和贡献新的配置。

接下来的内容中, 主要围绕两个工具的使用.

  • home-manager 声明式, 函数式, 面向终态的用户配置管理方案
  • 通过 yadm 完成将配置文件同步到 github
Read more »

rock960 是一块不太常见的 96boards 规范得的 rk3399 单板,目前厂家已经不维护了(email咨询得知).

不过还好他们邮件给了可以下载最新固件的地址 sd-card-images , 下载下来发现是 sd card 用的镜像, 所以需要这里说下怎么制作emmc 版本镜像

还需要稍微处理以下就可以得到更新的 debian 固件

接下来就介绍一下, 从原始的 96boards 镜像上更新 rootfs 为更新的版本的 debian 系统. 这样子从老的 boot 分区启动, 然后引导最新的Debian.
最完美的方式, 当然是boot分区也一起升级了, 这个以后再折腾了.

Read more »

总算来谈下这个烂大街的话题了。

面向对象一般会谈继承封装多态,但列举一门理论具备什么特征并不能帮助程序员合理地应用这门理论。

在传统面向数据和过程的编程方式中, 我们关心的东西有

  • 特定结构的数据
  • 针对数据定义的行为
1
2
struct {} A;
void function act(A a);

实话说,这样的编程手段对于解决问题本身已经够用了。

但对于现代化的软件工程项目,为了更好地解决开发效率与开发成本等问题,
我们需要引入一种更加有效的描述方式。使得开发任务更加顺畅, 开发成本能够有效降低。

java 界似乎在远古时期宣扬了一种基于 class 的编程方式,并将这种行为称作 oop 的有效实践。
而我们知道, 那端时期 java 界所宣扬的从来就没几个是靠谱的。

首先要摒弃所谓的 class 方式就是面向对象。
即使是 javascript 这种基于原型的编程语言,也是满足面向对象原则的。

只是纯粹描述数据本身的东西, 我们称之为 struct(结构体), 它具备了类型和实例两个内容。
而面向对象,则需要在结构体之上,增加一个可以描述行为的方式, 这种方式一般叫做 method (方法)
这里方法虽然看起来和 function 类似, 但是它是提供了数据绑定的, 而函数只和输入输出相关,不关心状态。

这个东西从实现上,仅仅是个手法而已,和上文所提到的结构体和函数的接合可以达到同样的目的。
现代化的编程语言,正是通过这样的变换手法,优化编程的模型而提高生产力的。

使用面向对象方式编程的开发者,关注的是某一个对象,它所具备的行为(method)。

好吧,因为类型的扩散,我们还是得考虑多种类型之间的交互, 和前面说的 函数 + 结构体 的方式,
差别并不大。
为了优化这中情况,程序员能做到的无非就是简化传入参数中的类型,尽量使用平坦的数据类型.

1
2
3
4
Name n = new Name("John");
Person p = new Person();
p.setName(n);
p.sayName();

上面的这个例子里, 我们倾向于认为 Name 是一个简单的类型, 而 Person 则是一种具有复杂行为的
高级类型。
但是,也正是面向对象语言提供的便捷手段,可以通过 封装 手段解决这个问题。

1
2
3
//JhonPerson extends Person
Person jp = new JhonPerson();
jp.sayName();

面向对象的本质在于 消息传递, 方法调用 不过是类 java 语言中的一种实现。
为了简化开发任务, 通过扩展组合等手段, 可以轻易地将复杂行为封装到新类型中,但是提供了相同的接口。

这个例子中, 我们可以体验到面向对象编程中的封装和多态.
封装重用了数据, 而多态重用了行为.

面向对象语言过度地提到继承, 似乎所有关联任务都可以通过 extends 来解决。
而我们知道,对象之间的关联关系, 扩展基类意味着需要继承行为和数据,是一种垂直的继承。
为了打破这种限制, 提供能够横向扩展的能力, java 引入了 接口 ,设计模式则提到了 组合.

前面提到,方法是一种和类型绑定的行为,如果只是垂直的单继承体系,多态只能在继承类型之间实现。
为了能够满足不同类型之间能够完善地支持多态,接口的引入解决了这个问题,但是,事情并没有能够完美地结束。

java 和其他类似语言中,由于函数并不是语言中的第一公民,接口仍然是一个具体的类型,只不过进行了一次间接的多继承.
到这里, 我们还是没能够得到我们希望的, 完全基于行为的多态。

在动态语言中,方法的调用是动态进行的,近似于在运行时直接查找同名方法,因此不存在这个问题。
而在静态语言中实现了类似行为的有 go , 虽然最终因为缺少泛型而导致了 interface {} 满天飞。

好不容易脱离了类型和方法的限制,又回到了如何解决方法和参数类型之间的绑定关系.

在书写 nginx 路由规则的时候, 得保证规则配置的规范, 否则维护起来成本很高。
本文简要地讨论了在 rewrite 模块的基础实现的简单路由规则,并解释了常用指令的使用细节。

rewrite 与 location

首先,这是一个不完整 nginx 虚拟主机配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
service {
root /service/http;
index index.php;

rewrite ^/api/(.*)+ /index.php?app=api&method=$1 break;
rewrite ^/index.php/(.*)+ /old_api_warning.html break;

location / {
return 200 "index";
}

location = /index.php {
return 200 "index.php";
}
}

上面的例子中, 指定的类型可以分为两种。

  • server,root, index, locationngx_http_core_module 提供的指令
  • rewrite 则是 ngx_http_rewrite_module 提供的指令之一, 同类的还有常用的 if

简单地概括, nginx 的处理配置流程如下

  1. 进行 rewrite 规则匹配,根据命中规则改写 location。
  2. 如果没有被其他指令中断并退出, 则进入 location 匹配。
  3. 执行匹配成功的 location 区块中的指令。

而对于 rewrite 系列指令和 location 指令的安排, 在书写配置的时候, 应该先定义好 location , 因为 location 是和 nginx 和后端服务沟通的桥梁。
再根据 location 的需要, 使用 rewrite 将各种各样用户输入的 url 正确地映射到 对应 locaton , 由 location 中的 proxy 指令转发给后端。

比如一个常规的 php 站点,有以下类似的 location 配置。

1
2
3
4
5
6
7
8
9
# 静态资源
location /assets {
root /service/http/assets/;
}
# 入口 php 脚本 位置
location /index.php {
root /service/http/phpsrc/webroot/;
# ... 若干 fast-cgi 代理规则
}
注意, 这份配置省略了不必要的指令,能够满足大部分的 php 应用的需要。

有时候,为了优化 url 的展示,或者兼容旧应用的 url 规则, 只是这么简单的两条 location 配置自然是不够用的。这时候我们可以通过 rewrite 规则进行弥补。

1
2
3
rewrite /index.html$ /app/page-default; //兼容旧 url

rewrite /app/page-(.*) /index.php?app=api&page=$1 break;

请注意这两条 rewrite 规则区别的在最后一个 flag 参数 break

 可用的 flag 参数还有 `permanent`, `redirect`, `last` 等, 后面分析它们之间的差别。

多出来的 break 参数作用会使得 rewirte 指令的跳转方式发生变化:

  • 没有 flag 的 rewrite 指令完成 location 改写之后 ,继续往下寻找其他 rewrite 规则, 看看有没有符合要求的。如果没有, 那么进入 location 匹配。
  • break, 跳过其他所有 rewrite 规则, 进入 location 匹配

这里简单总结一下:

  • 如果 某一条 rewrite 规则命中直接转发给后端应用,那么应该加上一个 break 标记。
  • 如果 rewrite 规则起补充作用,还需要其他规则配合完成, 那么不带第三个的 flag 参数。

以上说的 rewrite 规则属于 nginx 的内部重定向规则, 也就是说, 用户外部看到的 url 依然是他输入的 url , 而转给后端应用的 $uri , 则已经是 nginx 改写之后的结果。

如果需要进行显式地外部重定向, 需要借助 redirect , permanent 这两个 flag 进行 302 和 301 重定向.
它的行为和 break 类似, 区别在于 nginx 会中断流程, 通过 http 请求告诉用户端进行重定向,
也就是这次请求不需要进过后端服务, 由 nginx 全职负责。

rewrite /error.html$ /error2.html redirect;

break 和 last 区别

这两个 flag 都会中断当前 rewrite 流程, 不再继续匹配后续的 rewrite 指令。

wiki 上是这么写的

stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI

如果是在 server 的顶级部分, 那么它们的作用是相同, 跳过剩下的 rewrite 指定, 进入 location 匹配。
如果 rewrite 是在 server 区块顶级 if 内部, 和直接放在 server 下级的 rewrite 行为是一致的。

1
2
3
4
5
6
7
8
9
10
11
server {
rewrite /error1.html /error.html break;
rewrite /error2.html /error.html last;

if ( $arg_version = "1.1" ) {
rewrite /error3.html /error.html break;
}

location = error.html {
}
}

以上的 rewrite 的跳转行为是相同的, 进入 location 匹配流程。

而两者的区别, 在于当 rewrite 指令存在于 location 区块中时, 见例子

1
2
3
4
5
6
7
8
9
10
11
location = index.php {
if ( $arg_q = "" ) {
rewrite /index.php /error.html last;
}
}
location = error.html {
if ( $arg_test ~= "" ) {
rewrite /error.html /error-test.html break;
}
}

第一个 location , 如果 q 参数为空, 那么将展示错误页面。

第二个 location , 如果 test 参数不为空, 通过 rewrite 规则,使用另外一个 error-test.html,
但 location 不变

注意, 这里并没有一个 `location = error2.html` 的匹配规则

从这个 case 的结果, 可以明显得区分两个指令之间的细微差别

  • last 跳出 location 块, 重新进行 location 匹配
  • break 跳过 location 下的后续 rewrite 规则, 执行其他指令。

所以 last 的特殊在于重新进行 location 匹配, 这也就是为什么会从

locaton = index.php

转向

locaton = error.html

所以一般情况下, 使用 break 指令会相对安全, 不会造成循环重定向。

比如:

1
2
3
4
5
6
7
8
9
10
location = error.html {
rewrite /error.html /error.html break;

if ($arg_q = "") {
return 404 "not q";
}

fastcgi_pass 127.0.0.1:9000;
#其他 fastcgi 配置
}
1
2
3
4
location = error.html {
rewrite /error.html /error.html last;
fastcgi_pass 127.0.0.1:9000;
}

第一个例子,完成了 rewrite 指令之后,break 指令使得后续的其他 rewrite 规则失效, 接着进行 proxy.
第二个例子会导致不断地进行 location 匹配, 最终导致 nginx 返回 500.

返回结果是这样的:

1
2
3
4
5
6
HTTP/1.1 500 Internal Server Error
Server: nginx/1.6.3
Date: Sat, 18 Apr 2015 12:10:49 GMT
Content-Type: text/html
Content-Length: 192
Connection: close
注:nginx 会记录同一条 rewrite 规则的执行次数,如果超过10次,将自动触发 500 进行自我保护。

小结

这里构造了两组例子用来说明 lastbreak 两个 flag 参数行为上的不一致。
并不是说 rewrite 规则在 server 和 location 上下文中的行为不一致, 而是他们的行为特征很容易造成误解。

两者的本质区别在于是否重新进行 location 匹配,所以当在 location 上下文 进行 last rewrite时。
对于不熟悉 rewrite 指定的其他人容易造成误解。

所以还是前文所提到的观点, 尽可能地将 rewrite 和 location 离开来。 在 location 中进行 rewrite, 容易造成重定向问题。

由于 rewrite 模块的 `rewrite` 和 `if` 指令会使得 nginx
的路由规则出现较多的逻辑和分支跳转, 在维护性上是比较糟糕的,
并不推荐过多地进行使用, 本文只是从行为和特性上分析了这些指令,
并不代表支持过度使用 rewrite 指令。

return 指令的应用

rewrite 模块中, 有一条非常有用的指令 return, 用于直接返回客户端指定的状态码。
甚至支持指定文本内容和url, 相比起使用 rewrite 指令302进行曲线救国,要简便地多。

1
2
3
4
5
6
7
8
9
10
location = /index.php {
if ( $arg_q = "" ) {
return 302 /page_not_found.html;
}

if ( $arg_id = "" ) {
return 404 "page not found";
}
return 200 "hello";
}

对于常规的基于 url 提供服务的应用, 基础的 rewrite 指令配合已经足够完成大部分任务。

下一篇文章再聊聊基于条件,变量和更加复杂的上下文, 完成进行路由规则匹配.

0%