IcingTomato's Archive A Very Simple Knowledge Archive

【家庭网络改造】QNAP NAS计划任务自动删除米家摄像头三个月前的视频文件

去年8月29日在京东下单了一个QNAP威联通的TS-216,替换掉我家原来自己用树莓派3B+(Ubuntu Server + Samba)搭建的NAS。装了两个东芝P300 2TB的硬盘,开始组的RAID 1。后来发现家里面四五个摄像头每天的数据量大得很,没几天就把硬盘写满了。后面想想RAID 1不实在,家里没啥重要数据,今年回家的时候就格盘换成RAID 0了。但是米家摄像头数据还是很大,没几个月3.6TB就快写满了。 于是在米家APP中找到了视频存储时长设置选项,改成3个月: 米家APP中视频存储时长设置选项 改了之后NAS上面数据一点没动,应该是不会删除NAS上面的视频数据,所以就想着让NAS怎么自动删除米家摄像头三个月前的视频文件。 (以下所有操作需提前在NAS中开启SSH服务) 首先想到的是用crontab,但是QNAP的Linux系统是基于BusyBox的,crontab -e 这种命令有,但是不会运行成功,即便是加上 sudo ,也不能保存计划任务。 所以只能使用 vi 命令编辑计划任务文件,文件路径是 /etc/config/crontab ,在文件末尾加上: 0 * * * * cd /share/MiCam/xiaomi_camera_videos && bash auto_del.sh 这个命令的意思是每小时执行一次 auto_del.sh 脚本,脚本内容如下: #!/bin/bash # Set the base directory where random directories are located base_directory="./" # Calculate the date threshold for deletion (3 months ago) date_threshold=$(date --date='-3 months' +'%Y%m%d00') echo "Before $date_threshold will be deleted!" # Find all random directories and process them find "$base_directory" -maxdepth 1 -type d -name '[0-9a-f]*' | while read -r dir; do # Change directory to the random directory cd "$dir" || continue # Skip if directory change fails echo "Entry $dir." # Find and delete directories older than the threshold find . -maxdepth 1 -type d -name '??????????' | while read -r subdir; do if [[ "$(basename "$subdir")" -lt "$date_threshold" ]]; then echo "Deleting directory: $subdir" rm -rf "$subdir" # Remove the directory recursively fi done # Change back to the base directory cd .. done 手动运行结果如下: 因为米家摄像头会在设置的文件夹下创建 xiaomi_camera_videos 文件夹,然后在这个文件夹下以设备序列号(?)创建的很像乱码的文件夹,每个乱码文件夹下面又以年月日时为名的文件夹,所以这个脚本的作用是删除 xiaomi_camera_videos/?????????? 文件夹下三个月前的视频文件夹。所以脚本必须放在 /目标文件夹/xiaomi_camera_videos下:

【网络基础】什么是NAT?

NAT是一种地址转换技术,它可以将IP数据报文头中的IP地址转换为另一个IP地址,并通过转换端口号达到地址重用的目的。NAT作为一种缓解IPv4公网地址枯竭的过渡技术,由于实现简单,得到了广泛应用。 1 NAT解决了什么问题?  2 NAT的类型  3 NAT是如何工作的?  4 如何使用NAT?  NAT解决了什么问题? 随着网络应用的增多,IPv4地址枯竭的问题越来越严重。尽管IPv6可以从根本上解决IPv4地址空间不足问题,但目前众多网络设备和网络应用大多是基于IPv4的,因此在IPv6广泛应用之前,使用一些过渡技术(如CIDR、私网地址等)是解决这个问题的主要方式,NAT就是这众多过渡技术中的一种。 当私网用户访问公网的报文到达网关设备后,如果网关设备上部署了NAT功能,设备会将收到的IP数据报文头中的IP地址转换为另一个IP地址,端口号转换为另一个端口号之后转发给公网。在这个过程中,设备可以用同一个公网地址来转换多个私网用户发过来的报文,并通过端口号来区分不同的私网用户,从而达到地址复用的目的。 早期的NAT是指Basic NAT,Basic NAT在技术上实现比较简单,只支持地址转换,不支持端口转换。因此,Basic NAT只能解决私网主机访问公网问题,无法解决IPv4地址短缺问题。后期的NAT主要是指网络地址端口转换NAPT(Network Address Port Translation),NAPT既支持地址转换也支持端口转换,允许多台私网主机共享一个公网IP地址访问公网,因此NAPT才可以真正改善IP地址短缺问题。 NAT的类型 根据NAT转换是对报文中的源地址进行转换还是对目的地址进行转换,NAT可以分为源NAT、目的NAT和双向NAT,下面我们分别介绍这三种NAT类型。 SNAT(源NAT) 源NAT在NAT转换时,仅对报文中的源地址进行转换,主要应用于私网用户访问公网的场景。当私网用户主机访问Internet时,私网用户主机发送的报文到达NAT设备后,设备通过源NAT技术将报文中的私网IPv4地址转换成公网IPv4地址,从而使私网用户可以正常访问Internet。 根据转换时是否同时转换源端口号,源NAT可以细分为如下几种类型,详见下图。 源NAT分类 DNAT(目的NAT) 目的NAT在NAT转换时,仅对报文中的目的地址和目的端口号进行转换,主要应用于公网用户访问私网服务的场景。当公网用户主机发送的报文到达NAT设备后,设备通过目的NAT技术将报文中的公网IPv4地址转换成私网IPv4地址,从而使公网用户可以使用公网地址访问私网服务。 根据转换前后的地址是否存在一种固定的映射关系,目的NAT可以细分为如下几种类型,详见下图。 目的NAT分类 双向NAT 双向NAT指的是在转换过程中同时转换报文的源信息和目的信息。双向NAT不是一个单独的功能,而是源NAT和目的NAT的组合。双向NAT是针对同一条流,在其经过设备时同时转换报文的源地址和目的地址。双向NAT主要应用在同时有外网用户访问内部服务器和私网用户访问内部服务器的场景。 STUN中定义的NAT类型 在STUN标准中,根据私网IP地址和端口到NAT出口的公网IP地址和端口的映射方式,把NAT分为如下四种类型,详见下图。 STUN中定义的NAT类型 Full Cone NAT(完全锥型NAT) 所有从同一个私网IP地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网IP地址和端口(IP:Port)。并且,任何外部主机通过向映射的公网IP地址和端口发送报文,都可以实现和内部主机进行通信。 这是一种比较宽松的策略,只要建立了私网IP地址和端口与公网IP地址和端口的映射关系,所有的Internet上的主机都可以访问该NAT之后的主机。 Restricted Cone NAT(限制锥型NAT) 所有从同一个私网IP地址和端口(IP1:Port1)发送过来的请求都会被映射成同一个公网IP和端口号(IP:Port)。与完全锥型NAT不同的是,当且仅当内部主机之前已经向公网主机发送过报文,此时公网主机才能向私网主机发送报文。 Port Restricted Cone NAT(端口限制锥型NAT) 与限制锥型NAT很相似,只不过它包括端口号。也就是说,一台公网主机(IP2:Port2)想给私网主机发送报文,必须是这台私网主机先前已经给这个IP地址和端口发送过报文。 Symmetric NAT(对称NAT) 所有从同一个私网IP地址和端口发送到一个特定的目的IP地址和端口的请求,都会被映射到同一个IP地址和端口。如果同一台主机使用相同的源地址和端口号发送报文,但是发往不同的目的地,NAT将会使用不同的映射。此外,只有收到数据的公网主机才可以反过来向私网主机发送报文。 这和端口限制锥型NAT不同,端口限制锥型NAT是所有请求映射到相同的公网IP地址和端口,而对称NAT是不同的请求有不同的映射。 NAT是如何工作的? 根据前面的分类,我们分别从源NAT和目的NAT中各选一种NAT为代表,介绍其工作原理。其他类型的NAT虽然在转换时,转换的内容有细微差别,但是工作原理都相似,不再重复介绍。此外,双向NAT是源NAT和目的NAT的组合,双向NAT的工作原理也不再重复介绍。 NAPT工作原理 NAPT在进行地址转换的同时还进行端口转换,可以实现多个私网用户共同使用一个公网IP地址上网。NAPT根据端口来区分不同用户,真正做到了地址复用。 NAPT工作原理示意图 当Host访问Web Server时,设备的处理过程如下: 设备收到Host发送的报文后查找NAT策略,发现需要对报文进行地址转换。 设备根据源IP Hash算法从NAT地址池中选择一个公网IP地址,替换报文的源IP地址,同时使用新的端口号替换报文的源端口号,并建立会话表,然后将报文发送至Internet。 设备收到Web Server响应Host的报文后,通过查找会话表匹配到步骤2中建立的表项,将报文的目的地址替换为Host的IP地址,将报文的目的端口号替换为原始的端口号,然后将报文发送至Intranet。 NAT Server工作原理 使用NAT Server时,需要先在设备上配置公网地址和私网地址的固定映射关系。配置完成后,设备将会生成Server-Map表项,存放公网地址和私网地址的映射关系。该表项将一直存在除非NAT Server的配置被删除。 NAT Server工作原理示意图 内部Server的私网IPv4地址为192.168.1.2/24,对外的公网IPv4地址为1.1.1.10,端口号都为80,它们之间的映射关系在设备上已提前配置好。当Host访问Server时,设备的处理过程如下: 设备收到Internet上用户访问1.1.1.10的报文的首包后,查找并匹配到Server-Map表项,将报文的目的IP地址转换为192.168.1.2。 设备建立会话表,然后将报文发送至Intranet。 设备收到Server响应Host的报文后,通过查找会话表匹配到步骤2中建立的表项,将报文的源地址替换为1.1.1.10,然后将报文发送至Internet。 后续Host继续发送给Server的报文,设备都会直接根据会话表项的记录对其进行转换,而不会再去查找Server-map表项。 如何使用NAT? 前面已经介绍了,不同的NAT类型适用于不同的应用场景。下面介绍几种典型的NAT应用,帮助用户使用NAT。 私网用户通过NAPT访问Internet 在许多小区、学校和企业的私网规划中,由于公网地址资源有限,通常给私网用户分配私网IPv4地址。此时,可以配置源NAT来实现私网用户访问Internet。用户可以根据自己拥有的公网IPv4地址的个数,选择使用NAPT或者Easy IP。 当用户拥有的公网IP地址个数较多时,配置了NAT设备出接口的IP地址和其他应用之后,还有可用的空闲公网IP地址时,可以选择NAPT。NAPT使用地址池内的IPv4地址作为私网主机转换后的公网IPv4地址。如下图所示,在设备上配置NAPT,实现私网主机访问Internet功能。 私网用户通过NAPT访问Internet 私网用户通过Easy IP访问Internet 当用户拥有的公网IPv4地址个数较少时,配置了NAT设备出接口的IPv4地址和其他应用之后,没有可用的空闲公网IPv4地址时,可以选择Easy IP。Easy IP使用出接口的IPv4地址作为私网主机转换后的公网IPv4地址。如下图所示,在设备上配置Easy IP,实现私网主机访问Internet功能。 私网用户通过Easy IP访问Internet 公网用户通过NAT Server访问私网服务器 在某些场合,私网中有一些服务器需要向公网用户提供服务,比如私网中部署的一些Web服务器、FTP服务器等,NAT支持这样的应用,此时可以配置NAT Server来实现公网用户访问私网服务器。如下图所示,在设备上配置NAT Server,固定“公网IP地址+端口号”与“私网IP地址+端口号”间的映射关系,实现公网主机通过该映射关系访问私网服务器功能。 公网用户通过NAT Server访问私网服务器

【WSFC 学习笔记】如何理解 Windows Server 故障转移群集 的 Quorum

前言 最近一个月一直在学高可用的东西,其中 WSFC 是其中一个重要的组成部分。在学习 WSFC 的过程中,我发现了一个很重要的概念:Quorum,这个概念网上一找全是照着微软官方文档写的 仲裁配置选项。问有什么模型的时候头头是道,什么节点多数无见证啊、仅磁盘见证啊什么的,一问啥是仲裁(Quorum)和见证(Witness)的时候傻眼了。所以我决定写一篇文章来总结一下我对 Quorum 的理解。 仲裁(Quorum)是什么 仲裁(Quorum)是 WSFC 中的一个重要概念,它是用来保证群集中的节点之间能够达成一致的一个机制。在 WSFC 中,仲裁是通过仲裁资源来实现的,这个资源可以是磁盘、文件共享、或者是其他的仲裁资源。在 WSFC 中,仲裁资源的作用是用来保证群集中的节点之间能够达成一致,从而保证群集的正常运行。 依我看,Quorum 其实应该翻译成 有效参与仲裁的设备(数),因为 Quorum 本来就翻译成 法定人数/出席会议最小人数,不应该直接叫 仲裁。 现在涉及到 仲裁 这个法律概念,所以我们可以引入法律属于去解释。在法律中,仲裁 是指一种解决纠纷的方式,当纠纷发生时,双方可以通过仲裁的方式来解决纠纷。在法律仲裁中,仲裁员是一个独立的第三方,他们会根据法律和事实来做出裁决。但在 WSFC 中,仲裁员(节点/服务器/虚拟机)既是当事人(所有节点),又参与仲裁庭,它们会根据法律和事实(仲裁模型)来做出裁决,是用来保证群集中的节点之间能够达成一致的。 节点多数,无见证:由节点组织“合议制仲裁庭”,当事人(所有参与群集的节点/服务器)约定由奇数名仲裁员(节点)组成仲裁庭(偶数节点就使其中一个节点下线),其中一名为首席仲裁员(主节点/服务器);如果是双节点就采取“独任制仲裁庭”,只推举一名仲裁员(一个节点做主服务器,另外一个下线)。 磁盘、文件共享见证:见证人是证人之外知道案件情况的当事人(所以磁盘、文件共享见证需要对所有节点可见,在注册表中节点文件是Cluster,见证文件是0.Cluster,可以证明见证确实是“当事人”)。见证人不参与仲裁庭,但是可以提供证据(见证文件)。 总结来说: 节点多数,无见证: 合议庭:集群的所有活动节点。 仲裁庭成员:每个节点都可以投票决定集群状态,若节点总数为偶数,可能需要让一个节点下线以避免平票。 首席仲裁员(主节点):在实际的WSFC中,通常没有固定的“首席仲裁员”或主节点,所有节点理论上是平等的,但在实践中,某些节点可能因为资源位置或网络优势暂时承担更多责任。 双节点集群的“独任制”: 这种情况下,通常需要额外的见证(磁盘或文件共享见证),因为单纯的两个节点在一个节点失效时无法决定集群状态。如果不使用见证,确实可能会推举一个节点为主导,另一个则在主节点活跃时处于待命状态。 磁盘、文件共享见证: 见证人:在这种情况下,见证(文件共享或磁盘)充当了“知情人”,它存储关于集群配置的关键信息,确保在节点间的意见不一致时提供“证据”来帮助做出决策。见证的存在特别在节点数为偶数时非常关键,以避免平票问题。 但是大多数人对法律仲裁制度不了解,而且法律仲裁制度和 WSFC 中的仲裁机制有很大的不同,所以我觉得这个比喻不太合适。 所以我决定引入日常生活,比如双节点集群就像情侣之间决定晚餐吃什么,引入磁盘见证相当于三口之家中孩子的存在,这样比喻起来更加贴近生活,也更容易理解。 原文如下: Mode Description Node majority (no witness) 节点多数 Only nodes have votes. No quorum witness is configured. The cluster quorum is the majority of voting nodes in the active cluster membership. Node majority with witness (disk or file share) 节点多数+见证 Nodes have votes. In addition, a quorum witness has a vote. The cluster quorum is the majority of voting nodes in the active cluster membership plus a witness vote. A quorum witness can be a designated disk witness or a designated file share witness. No majority (disk witness only) 仅磁盘见证 No nodes have votes. Only a disk witness has a vote. The cluster quorum is determined by the state of the disk witness. Generally, this mode is not recommended, and it should not be selected because it creates a single point of failure for the cluster. 我们可以这样理解: 仲裁模型 描述 男生女生二人决定晚餐 节点多数 【双节点】男生让渡(即其中一节点下线,有一票投票权但动态见证干预其不投票),让女生(主节点/服务器/虚拟机 Owner Node)来选择点什么外卖或者出去吃什么餐馆。你就负责买单就行。 【三节点及以上】女生带闺蜜来了,女生和闺蜜手拉手胳膊挽胳膊掌握晚餐选择主动权(即具有集群的控制权,在一个三节点集群中,通常需要至少两个节点在线并相互通信,以维持集群的正常操作)。象征性问你一下想吃什么。所以你还是负责买单的小丑。 核心家庭(三口之家)或扩展家庭(爸妈或者和爷爷奶奶外公外婆一起为节点,孩子为见证) 节点多数+见证 爸妈想吃啥就做啥,你的意见仅供参考 因为爷爷奶奶外公外婆说孩子想吃这个那个,所以爸妈做饭就做了大家都爱吃的东西。(换句话说见证也参与,而不仅仅是节点多数) 你过生日那天 仅磁盘见证 “妈,我生日想吃开封菜。” “不行,油炸食品不健康。” (别看上面的见证是你,但是这一条就不是你了) 总结 给我写乐了。宿舍停水了,舍友用我的博客洗完了澡。

【Intel x86 IoT系列】Intel Arduino/Genuino 101 简单测评

注:下文中 Intel Arduino/Genuino 101 简称为 Arduino 101 Arduino 101 Arduino/Genuino 101 的由来 2015年10月16日1,距离世界上第一台商用可编程计算器 Olivetti Programma 1012 问世过去了整整50年。为了致敬这个伟大的项目,英特尔和 Massimo Banzi3(Arduino项目的联合创始人)在 Maker Faire 宣布推出一款新的开发板,名为Arduino/Genuino 101 —— 一款专为教育用途、创客世界和首次接触编程的人设计的开发板。 Olivetti Programma 101 Arduino/Genuino 101 的硬件和软件 Arduino 101是一款学习和开发板,以入门级价格提供英特尔® Curie™ 模块的性能和低功耗以及 Arduino 的简单性。它保留了与 UNO 相同的强大外形和外设列表,并增加了板载低功耗蓝牙®(Bluetooth Low Energy,BLE,Nordic 的 nRF512822)功能和 6 轴加速度计/陀螺仪(Bosch 的 BMI160)。 Arduino 101 使用的 Curie™ 模块属于异构双核,包含两个微型内核,一个 x86 (Quark SE,SE 即 Second Edition) 和一个 32 位 ARC 架构内核,时钟频率均为 32MHz。英特尔工具链可在两个内核上以最佳方式编译 Arduino 草图,以完成要求最苛刻的任务。英特尔开发的实时操作系统 (Zephyr) 和框架是开源的。其中,ARC架构是一种32位的RISC处理器架构,由 Synopsys(新思科技)开发。 101 带有 14 个数字输入/输出引脚(其中 4 个可用作 PWM 输出)、6 个模拟输入、一个用于串行通信和草图上传的 USB 连接器、一个电源插孔、一个带 SPI 信号的 ICSP 接头和 I2C 专用引脚。电路板工作电压和 I/O 为 3.3V,但所有引脚均具有 5V 过压保护。 相较于标准 Arduino Uno, Arduino 101 采用的异构双核的 Curie™ 模块比 8位的 Atmel 328p 微控制器更强大,存储空间也更大(Intel 官方的 Curie 规格是 384KB Flash 与 80KB SRAM ,但相关报导写 196KB Flash 与 24KB RAM,Arduino 官网写 384KB Flash 与 80KB SRAM,但 SRAM 部分注明只有 24KB 可供应用程序 Sketch 使用。因此,推估之所以写 196KB Flash,应该也是系统占据一部分,真正可供运用的是 196KB。除此之外电路板上似乎又增设2MB Flash 可供 Curie 使用) 在2016年4月21日,英特尔发布 Arduino 101 固件源代码。包含用于 101 上 Curie 处理器的完整 BSP(板级支持包)。它允许您编译和修改核心操作系统和固件,以管理更新和引导加载程序。固件在 Curie 模块内的 x86 芯片上运行,并使用 回调 与 ARC 内核(运行 Arduino 程序)进行通信。x86 内核负责处理低功耗蓝牙® (BLE) 和 USB 通信,从而减轻 ARC 内核的负担。4 技术规格5 Microcontroller Intel Curie Operating Voltage 3.3V (5V tolerant I/O) Input Voltage (recommended) 7-12V Input Voltage (limit) 7-17V Digital I/O Pins 14 (of which 4 provide PWM output) PWM Digital I/O Pins 4 Analog Input Pins 6 DC Current per I/O Pin 20 mA Flash Memory 196 kB SRAM 24 kB Clock Speed 32MHz LED_BUILTIN 13 Features Bluetooth® Low Energy, 6-axis accelerometer/gyro Length 68.6 mm Width 53.4 mm Weight 34 gr. Arduino 代码实现 Blink #include <Arduino.h> void setup() { Serial.begin(9600); while (!Serial) { delay(10); // wait for serial port to connect. Needed for native USB port only } pinMode(LED_BUILTIN, OUTPUT); } void loop() { digitalWrite(LED_BUILTIN, HIGH); Serial.println("LED ON!"); delay(1000); digitalWrite(LED_BUILTIN, LOW); Serial.println("LED OFF!"); delay(1000); } 读取板载 IMU #include <Arduino.h> #include "CurieIMU.h" void setup() { Serial.begin(9600); while (!Serial) { delay(10); } // Start the acceleromter CurieIMU.begin(); // Set the accelerometer range to 2G CurieIMU.setAccelerometerRange(2); } void loop() { // read accelerometer: int x = CurieIMU.readAccelerometer(X_AXIS); int y = CurieIMU.readAccelerometer(Y_AXIS); int z = CurieIMU.readAccelerometer(Z_AXIS); Serial.print("x: "); Serial.print(x); Serial.print(" y: "); Serial.print(y); Serial.print(" z: "); Serial.print(z); Serial.println(""); } 读取板载 RTC #include <Arduino.h> #include <CurieTime.h> void setup() { Serial.begin(9600); while (!Serial) { delay(10); } setTime(1, 23, 24, 25, 1, 2024); } void loop() { //create a character array of 16 characters for the time char clockTime[16]; //use sprintf to create a time string of the hour, minte and seconds sprintf(clockTime, "%2d:%2d:%2d", hour(), minute(), second()); //create a character array of 15 characters for the date char dateTime[16]; //use sprintf to create a date string from month, day and year sprintf(dateTime, "%2d/%2d/%4d", month(), day(), year()); //print the time and date to the serial monitor Serial.print(clockTime); Serial.println(dateTime); delay(1000); } 使用板载 BLE 控制 LED 需要配合Nordic nRF Connect使用,可以在 Google Play Store 或 App Store 下载 #include <Arduino.h> #include <CurieBLE.h> BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service // BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central BLEUnsignedCharCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); const int ledPin = 13; // pin to use for the LED void setup() { Serial.begin(9600); // set LED pin to output mode pinMode(ledPin, OUTPUT); // begin initialization BLE.begin(); // set advertised local name and service UUID: BLE.setLocalName("Arduino 101"); BLE.setAdvertisedService(ledService); // add the characteristic to the service ledService.addCharacteristic(switchCharacteristic); // add service BLE.addService(ledService); // set the initial value for the characeristic: switchCharacteristic.setValue(0); // start advertising BLE.advertise(); Serial.println("BLE LED Peripheral"); } void loop() { // listen for BLE peripherals to connect: BLEDevice central = BLE.central(); // if a central is connected to peripheral: if (central) { Serial.print("Connected to central: "); // print the central's MAC address: Serial.println(central.address()); // while the central is still connected to peripheral: while (central.connected()) { // if the remote device wrote to the characteristic, // use the value to control the LED: if (switchCharacteristic.written()) { if (switchCharacteristic.value()) { // any value other than 0 Serial.println("LED on"); digitalWrite(ledPin, HIGH); // will turn the LED on } else { // a 0 value Serial.println(F("LED off")); digitalWrite(ledPin, LOW); // will turn the LED off } } } // when the central disconnects, print it out: Serial.print(F("Disconnected from central: ")); Serial.println(central.address()); } } 后记 Arduino 101 于 2016年 Q1 季度发布。仅仅过去一年有余,在英特尔宣布 Galileo,Edison 和 Joule 模块停产一个月后,也草草停产 Curie 模块(07/17/2017)6,只能说是非常可惜,英特尔宏图壮志准备在 IoT 领域大展拳脚,但是却因为种种原因,最终只能黯然收场。也导致诸多使用 Curie 模块的企业被迫更换产品线,比如小米智能跑鞋。 图片源自Intel Curie Module, Arduino 101 Board Are Being Discontinued (Too) - CNX Software 《Arduino程序设计基础》 的作者 陈吕洲 先生对 Arduino 101 抱有极高的评价:Genuino 101是一个极具特色的Arduino开发板,它基于Intel Curie模组,不仅有着和Arduino UNO一样特性和外设,还集成了低功耗蓝牙(BLE)和六轴姿态传感器(IMU)功能,借助intel Curie模组上模式匹配引擎,甚至可以进行机器学习操作。因此使用Genuino 101,可以完成一些传统单片机或者Arduino难以胜任的工作,制作更为惊艳的作品。为此他本人还专门为 Arduino 101 著书 —— 《Arduino 101 开发入门》7。 不过 Arduino 101 也有三点比标准 Arduino Uno 差8: 只有4组 PWM 脉宽调变输出,Uno 有6组; 没有任何的 EEPROM 存储,Uno 至少还有1KB可以使用; 单一 I/O 的电流驱动能力最高仅4mA,Uno 可到20mA。 所以 Curie 只是引脚排列与 Arduino 相仿,不能完全保证原有设计电路或者 Arduino Shield可完全相容(兼容)沿用、续用。 如果再与 MediaTek 的 LinkIt ONE 小比一下,LinkIt One也有效能更佳的处理器核心与更多容量的存储,且依然提供EEPROM 可用,但 PWM 方面则只有2组。当然,LinkIt ONE 强在无线通讯(日后会测评一下),如 GPRS、Wi-Fi、GPS等,Curie 略强在惯性感测。 英特尔似乎在 IoT 领域并没有取得太大的成就,但是英特尔的 x86 芯片却是世界上最流行的 CPU 架构,这也是英特尔的核心竞争力,所以英特尔在 IoT 领域的失败并不会影响到英特尔的核心业务,但是英特尔的 IoT 产品却是非常有趣的,比如 Edison、Galileo、Curie、Arduino 101 等等,这些产品都是英特尔的 IoT 产品,但是英特尔并没有将这些产品做成一个系列,而是分散在各个不同的系列中,这也是英特尔在 IoT 领域失败的一个原因。 笔者手上的 Genuino 101 是 2021年在 SeeedStudio 任职时,从 FAE 仓库里找到的,当时已经停产了,但是还是有一些库存,所以就拿了一块回来,但是一直没有时间去折腾,直到最近才拿出来玩一玩,但是发现 Arduino 101 的资料实在是太少了,所以就写了这篇文章,谨此纪念一家伟大的公司一个伟大的产品。 引用 List of Arduino boards and compatible systems - Wikipedia Programma 101 - Wikipedia Massimo Banzi - massimobanzi.com Intel releases the Arduino 101 firmware source code - Arduino Arduino 101 - Arduino Arduino 101 - Intel 《Arduino 101 开发入门》 - XX到此一游 Arduino 101擁抱Intel Curie核心 優缺點比一比 - 陸向陽 MakerPro

通过 adb 侧载低版本 SDK 的安卓应用

前些日子翻出来自己高中写的安卓应用,安装上去看看,但是发现小米的 HyperOS 装不上去,一直报错: 安装失败 (-29) 失败原因 安装包与系统不兼容 我第一反应应该是因为应用的目标 SDK 版本(19)太低了,先用 App Cloner 改改参数,改到 SDK26 看看。 还是不行,打开是能打开但是白屏。 最后看看用 adb 侧载算了: 安全性 最低可安装的目标 API 级别 从 Android 14 开始,targetSdkVersion 低于 23 的应用无法安装。要求应用满足这些最低目标 API 级别要求有助于提高用户的安全性和隐私性。 恶意软件通常会以较旧的 API 级别为目标平台,以绕过在较新版本 Android 中引入的安全和隐私保护机制。例如,有些恶意软件应用使用 targetSdkVersion 22,以避免受到 Android 6.0 Marshmallow(API 级别 23)在 2015 年引入的运行时权限模型的约束。这项 Android 14 变更使恶意软件更难以规避安全和隐私权方面的改进限制。尝试安装以较低 API 级别为目标平台的应用将导致安装失败,并且 Logcat 中会显示以下消息: INSTALL_FAILED_DEPRECATED_SDK_VERSION: App package must target at least SDK version 23, but found 7 在升级到 Android 14 的设备上,targetSdkVersion 低于 23 的所有应用都将继续保持安装状态。 如果您需要测试以旧版 API 级别为目标平台的应用,请使用以下 ADB 命令: adb install --bypass-low-target-sdk-block FILENAME.apk

Git 推送的配置

Git 是个很神奇的东西,发掘的越深,好玩的越多。 Git 代理设置 git config --global http.proxy http://proxyUsername:proxyPassword@proxy.server.com:port git config --global https.proxy http://proxyUsername:proxyPassword@proxy.server.com:port git push 到两个地址的仓库 好比我这个博客仓库,在国内因为某些不可抗力,github 无法访问,所以我在 gitee 上也有一个仓库,这样就可以在国内访问了。但是我每次都要推送两次,很麻烦。或者要登陆 gitee 的仓库,然后按一下同步按钮,也很麻烦。 所以我就想,能不能只推送一次,然后两个仓库都更新呢? # 原始推送地址 https://github.com/IcingTomato/icing.fun.git # 添加第二个推送地址 git remote set-url --add origin https://gitee.com/IcingTomato/icing.fun.git # 查看推送/拉取地址 git remote -v # origin https://github.com/IcingTomato/icing.fun.git (fetch) # origin https://github.com/IcingTomato/icing.fun.git (push) # origin https://gitee.com/IcingTomato/icing.fun.git (push) # 推送 git push Git 不公开邮箱账户 之前貌似更新了什么,好像是将保密你的邮箱地址,并在执行基于 Web WebIDE 的 Git 操作中,使用 xxxx@user.noreply.xxx.com 作为你的邮箱地址。如果你希望命令行 Git 操作使用你的私人邮箱地址,你必须在 Git 中设置你的邮箱地址。 # 我经常用 GitHub 的邮箱,全局就设置成这个了 git config --global user.email "xxxx@users.noreply.github.com" # cd 到 icing.fun 仓库目录下 git config user.email "xxxx@users.noreply.github.com" git config user.email "xxxx@users.noreply.gitee.com"

GitHub 的三种 Pull Request

在 GitHub 上处理 Pull Requests(PRs)时,有三种主要的合并策略:创建合并提交(Merge Commit)、挤压合并(Squash and Merge)、变基合并(Rebase and Merge)。下面简要解释每种策略的特点及其区别: 1. 创建合并提交(Merge Commit) 操作:当你选择创建合并提交时,GitHub 会创建一个新的合并提交来将 PR 的更改合并到基分支(如 main 或 master)。这个合并提交会包含一个特殊的提交信息,通常包括一个指向 PR 的引用。 结果:在基分支的提交历史中,PR 的所有提交都将被保留,并附加一个额外的合并提交。这保持了完整的历史记录和PR的独立性。 适用场景:当你想保留对 PR 的每一次单独提交的完整历史时。 2. 挤压合并(Squash and Merge) 操作:挤压合并会将 PR 中的所有提交合并成一个单独的提交,然后将这个提交合并到基分支。 结果:基分支的提交历史更简洁,因为它只包含一个代表整个 PR 的提交。但这意味着PR中原始提交的细节被压缩。 适用场景:适用于PR包含许多小的、渐进的更改,但你想在基分支上保持一份干净、未混杂的历史记录。 3. 变基合并(Rebase and Merge) 操作:变基合并首先会将 PR 的提交历史变基到目标分支的最新提交上,然后将这些提交直接加入到基分支,而不创建额外的合并提交。 结果:这在基分支上产生一条线性的提交历史,但不会保留 PR 作为独立实体的信息。 适用场景:当你希望保持线性历史且避免合并提交时。这通常适用于较小的、清晰的更改。 总结 创建合并提交保留了所有的提交历史和PR的独立性,增加了一个额外的合并提交。 挤压合并将PR的所有提交压缩成一个单独的提交,使历史更干净。 变基合并保持了线性的提交历史,但不会创建合并提交。 选择哪种策略取决于你的项目团队如何希望管理其版本控制历史的清晰度和连贯性。

RaspberryPi 启动流程探究

树莓派学习第一步,探究树莓派的启动流程。 树莓派的启动流程 相较于一般/通常的 ARM SoC 来说,树莓派1/2/3/Zero的启动流程有些不同,这里简单的记录一下。 树莓派1/2/3/Zero的启动流程 在开机时,CPU是离线的,由GPU上的一个小型RISC核心负责启动SoC,因此大部分启动组件实际上是在GPU代码上运行,而不是CPU上。 启动顺序如下: 第一阶段引导程序:用于挂载SD卡上的FAT32启动分区,以便可以访问第二阶段引导程序。它在制造树莓派时已经烧录到SoC本身,并且用户无法重新编程。 第二阶段引导程序(bootcode.bin):用于从SD卡检索GPU固件,加载固件,然后启动GPU。 GPU固件(start.elf):一经加载,允许GPU启动CPU。另一个文件fixup.dat用于配置GPU和CPU之间的SDRAM分区。此时,CPU从复位模式释放并转移到CPU上执行。 用户代码(User Code):这可以是任何数量的二进制文件之一。默认情况下,它是Linux内核(通常命名为kernel.img),但它也可以是另一个引导程序(例如U-Boot)或一个简单的应用程序。 在2012年10月19日之前,曾经还有第三阶段引导程序(loader.bin),现已废除。 下面是原文elinux.org/RPi_Software At power-up, the CPU is offline, and a small RISC core on the GPU is responsible for booting the SoC, therefore most of the boot components are actually run on the GPU code, not the CPU. The boot order and components are as follows: First stage bootloader - This is used to mount the FAT32 boot partition on the SD card so that the second stage bootloader can be accessed. It is programmed into the SoC itself during manufacture of the RPi and cannot be reprogrammed by a user. Second stage bootloader (bootcode.bin) - This is used to retrieve the GPU firmware from the SD card, program the firmware, then start the GPU. GPU firmware (start.elf) - Once loaded, this allows the GPU to start up the CPU. An additional file, fixup.dat, is used to configure the SDRAM partition between the GPU and the CPU. At this point, the CPU is release from reset and execution is transferred over. User code - This can be one of any number of binaries. By default, it is the Linux kernel (usually named kernel.img), but it can also be another bootloader (e.g. U-Boot), or a bare-bones application. Prior to 19th October 2012, there was previously also a third stage bootloader (loader.bin) but this is no longer required.[1] 树莓派4B(BCM2711)的启动流程 树莓派4B(BCM2711)因为某些硬件升级导致启动流程复杂了不是一丁半点[2]。 当树莓派4开机时,引导过程涉及BCM2711 VideoCore VI 处理器单元(VPU)的操作。以下是引导过程的步骤概述: VPU核心0的初始化: 开机时,BCM2711芯片的VPU核心0开始运行。 程序计数器设置为0x60000000,映射到片上启动ROM。 VPU频率和启动ROM: 最初,VPU的频率设置为振荡器频率,树莓派4上为54.0 MHz。 这个低频率足以完成初始引导任务。 读取OTP寄存器: 启动ROM读取某些一次性可编程(OTP)寄存器(17/18, 66, 和 67)。 这些寄存器与树莓派3相比有不同的含义。更多信息可以在这个GitHub问题中找到。 引导指示引脚: 如果配置了“引导指示引脚”,则它会被激活几毫秒。 USB设备模式恢复: 如果配置了USB设备模式强制引导引脚且激活,某些步骤会被跳过。 这是一种从USB启动恢复的机制。 SD卡引导: 如果SD卡引导引脚处于激活状态或未配置,启动ROM尝试从SD卡的第一个FAT分区加载recovery.bin。 这种机制有助于在EEPROM损坏时恢复板子。更多细节可以在树莓派硬件文档中找到。 从EEPROM加载固件: BootROM尝试从EEPROM芯片加载固件,通常是通过SPI0在GPIO 40–43接口的Winbond W25X40芯片。 USB设备模式: 如果前面的步骤失败,芯片进入通过Type-C连接器的USB设备模式。 它等待从USB主机获取恢复映像,需要在主机上更新usbboot。更多信息可以在这个拉取请求中找到。 在这种模式下,VPU的频率提升到100 MHz。 固件映像签名: 需要在固件映像中附加一个20字节的签名,这是使用存储在OTP中的通用密钥的映像的HMAC-SHA1散列。 这个密钥无法通过树莓派提供的固件的API访问。 这种机制最初在树莓派3上是可选的,现在则是强制性的,尽管它并不提供安全保护,因为存在一个通用的recovery.bin映像。 这个引导过程突出显示了确保树莓派4能够成功引导并从潜在的固件问题中恢复的各种机制。 When the Raspberry Pi 4 is powered on, the BCM2711 VPU core 0 gets started. The program counter is set to 0x60000000. This address maps to the on-chip boot ROM. Note that the VPU frequency is set to the oscillator (54.0 MHz on RPi4), so it’s not very fast, but it does not do a lot of things either: Read boot-related OTP registers (17/18, 66 and 67). Note that these registers have a different meaning than on Raspberry Pi 3. More information can be found here. If a “boot indication pin” is configured, it is activated for some milliseconds. If USB device-mode recovery force boot pin is configured and active, steps 4 and 5 are skipped. If SD card boot pin is either active or not configured, the boot ROM tries to load recovery.bin from the first FAT partition. This provides a mechanism to unbrick your board if the EEPROM gets corrupted. More details can be found here. Next, the boot ROM tries to load firmware from the EEPROM chip. This is a standard Winbond W25X40 chip, access through SPI0 on GPIO 40–43. If everything else fails, the chip enters USB device mode (on the Type-C connector) and waits to get the recovery image from the USB host. You need an updated usbboot on the host to use this method. See also this pull request. The VPU frequency is increased to 100 MHz for the USB device mode. Note that a 20-byte signature must be appended to the firmware image. It is an HMAC-SHA1 hash of the image using a universal key that is also stored in the OTP, but not accessible using the Foundation firmware APIs. A similar mechanism was available as an optional feature on Raspberry Pi3. I originally assumed that the key is unique for each Raspberry Pi, but since there is one universal recovery.bin image that works for all, there must be only one key. Given that it provides no security, I’m not sure why this hash is now mandatory.

Jekyll Learning

Jekyll/Liquid 博客学习记录(连载) Jekyll博客配置教程 我是LNMP派,因为我不会用Apache配置。 然而静态博客没必要MySQL, MariaDB, PHP。所以我们只安装Jekyll所有必需的依赖项。 sudo apt-get install ruby-full build-essential zlib1g-dev nginx sudo apt-get install gcc g++ make curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update && sudo apt-get install yarn 然而Ubuntu 16.04太老了,ruby的版本不支持Jekyll 4。所以我们要手动安装Ruby 3.0.0。 第一步是为Ruby安装一些依赖项。一步一步运行。 sudo apt install curl curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list sudo apt-get update sudo apt-get install git-core zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev nodejs yarn 接下来,我们将使用以下三种方法之一安装Ruby。每个都有自己的好处,如今大多数人都喜欢使用rbenv,但是如果您熟悉rvm,也可以按照这些步骤进行操作。我也提供了从源代码安装的说明,但是一般而言,您需要选择rbenv或rvm。 方法一:使用rbenv安装。首先安装rbenv,然后安装ruby-build: cd git clone https://github.com/rbenv/rbenv.git ~/.rbenv echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc echo 'eval "$(rbenv init -)"' >> ~/.bashrc exec $SHELL git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc exec $SHELL rbenv install 3.0.0 rbenv global 3.0.0 ruby -v 方法二:RVM安装 sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm rvm install 3.0.0 rvm use 3.0.0 --default ruby -v 方法三:Ruby源码安装 cd wget http://ftp.ruby-lang.org/pub/ruby/3.0/ruby-3.0.0.tar.gz tar -xzvf ruby-3.0.0.tar.gz cd ruby-3.0.0/ ./configure make sudo make install ruby -v Ruby服务器在国外,我的博客服务器在国内,所以换个Ruby源 gem source -r https://rubygems.org/ gem source --add https://gems.ruby-china.com/ gem source -u 最后一步是安装Bundler gem install bundler jekyll github-pages jekyll-paginate webrick 安装完之后就可以将博客从GitHub或Gitee上git clone下来了。 接着完成对nginx的配置 安装nginx sudo apt-get install nginx 然后定位到nginx的配置文件: sudo vim /etc/nginx/sites-enabled/default 配置文件在下面: pi@ubuntu:~cat /etc/nginx/sites-enabled/default server { # 监听80端口,我嫌麻烦就懒得加SSL了 listen 80 default_server; listen [::]:80 default_server; # 网页在本地的根目录 root /var/www/html; # 显示的主页 index index.html; # 你的域名 server_name domain.yours; # 404,403配置文件 # 主要目的是当出现404或403时直接回显200 error_page 404 =200 /404.html; # 404 location /404.html { root /var/www/html/; internal; } # 403 error_page 403 =200 /offline.html; # 404 location /offline.html { root /var/www/html/; internal; } } 保存好之后重启nginx sudo systemctl restart nginx Jekyll博客维护教程 博客的维护教程修改自 Hux 和 BY_Blog 环境 如果你安装了 Jekyll,那你只需要在命令行输入jekyll serve 或 jekyll s就能在本地浏览器中输入http://127.0.0.1:4000/预览主题,对主题的修改也能实时展示(需要强刷浏览器,Ctrl+F5)。 开始 你可以通用修改 _config.yml文件来轻松的开始搭建自己的博客: # Site settings title: 啥玩意儿啊这 # 你的博客网站标题 SEOTitle: 啥玩意儿 | What's this # SEO 标题 description: "Hey" # 随便说点,描述一下 # SNS settings github_username: null # 你的github账号 # Build settings # paginate: 100 # 一页你准备放几篇文章 Jekyll官方网站还有很多的参数可以调,比如设置文章的链接形式…网址在这里:Jekyll - Official Site 中文版的在这里:Jekyll中文. 撰写博文 要发表的文章一般以 Markdown 的格式放在这里_posts/,你只要看看这篇模板里的文章你就立刻明白该如何设置。 yaml 头文件长这样: --- layout: post title: 硕人其颀 衣锦褧衣 subtitle: Quark核心板的GPIO推算过程 date: 2021-02-01 author: null header-img: img/2021/02/01/01/title.jpg catalog: true tags: - Quark - GPIO - Linux - Project-Quantum - Nano Pi --- 侧边栏 看右边 设置是在 _config.yml文件里面的Sidebar settings那块。 # Sidebar settings sidebar: true #添加侧边栏 sidebar-about-description: "简单的描述一下你自己" sidebar-avatar: /img/avatar-by.jpg #你的大头贴,请使用绝对地址.注意:名字区分大小写!后缀名也是 侧边栏是响应式布局的,当屏幕尺寸小于992px的时候,侧边栏就会移动到底部。具体请见bootstrap栅格系统 Mini About Me Mini-About-Me 这个模块将在你的头像下面,展示你所有的社交账号。这个也是响应式布局,当屏幕变小时候,会将其移动到页面底部,只不过会稍微有点小变化,具体请看代码。 Featured Tags 看到这个网站 Medium 的推荐标签非常的炫酷,所以我将他加了进来。 这个模块现在是独立的,可以呈现在所有页面,包括主页和发表的每一篇文章标题的头上。 # Featured Tags featured-tags: true featured-condition-size: 1 # A tag will be featured if the size of it is more than this condition value 唯一需要注意的是featured-condition-size: 如果一个标签的 SIZE,也就是使用该标签的文章数大于上面设定的条件值,这个标签就会在首页上被推荐。 内部有一个条件模板 {% if tag[1].size > {{ site.featured-condition-size}} %} 是用来做筛选过滤的. Social-media Account # SNS settings RSS: true bilibili_username: bilibili_uid zhihu_username: username facebook_username: username github_username: username weibo_username: username Friends 友链部分。这会在全部页面显示。 设置是在 _config.yml文件里面的Friends那块,自己加吧。 # Friends friends: [ { title: "huohuo", href: "http://null.fun/" }, { title: "Apple", href: "https://apple.com/" } ] Keynote Layout HTML5幻灯片的排版: 这部分是用于占用html格式的幻灯片的,一般用到的是 Reveal.js, Impress.js, Slides, Prezi 等等.我认为一个现代化的博客怎么能少了放html幻灯的功能呢~ 其主要原理是添加一个 iframe,在里面加入外部链接。你可以直接写到头文件里面去,详情请见下面的yaml头文件的写法。 --- layout: keynote iframe: "http://huangxuan.me/js-module-7day/" --- iframe在不同的设备中,将会自动的调整大小。保留内边距是为了让手机用户可以向下滑动,以及添加更多的内容。 Comment 博客不仅支持 Disqus 评论系统,还加入了 Gitalk 评论系统,支持 Markdwon 语法,cool~ Disqus 优点:国际比较流行,界面也很大气、简洁,如果有人评论,还能实时通知,直接回复通知的邮件就行了; 缺点:评论必须要去注册一个disqus账号,分享一般只有Facebook和Twitter,另外在墙内加载速度略慢了一点。想要知道长啥样,可以看以前的版本点这里 最下面就可以看到。 Note:有很多人反映 Disqus 插件加载不出来,可能墙又架高了,有条件的话翻个墙就好了~ 使用: 首先,你需要去注册一个Disqus帐号。 其次,你只需要在下面的 yaml 头文件中设置一下就可以了。 # 评论系统 # Disqus(https://disqus.com/) disqus_username: Gitalk 优点:界面干净简洁,利用 Github issue API 做的评论插件,使用 Github 帐号进行登录和评论,最喜欢的支持 Markdown 语法,对于程序员来说真是太 cool 了。 缺点:配置比较繁琐,每篇文章的评论都需要初始化。 使用: 参考我的这篇文章:《屡顾尔仆 不输尔载-Jekyll博客迁移计划:Gitalk 插件与 Google Analytics 的配置》 Analytics 网站分析,现在支持百度统计和Google Analytics。需要去官方网站注册一下,然后将返回的code贴在下面: # Baidu Analytics ba_track_id: # # Google Analytics ga_track_id: 'UA-' # 你用Google账号去注册一个就会给你一个这样的id ga_domain: # 默认的是 auto, 这里我是自定义了的域名,你如果没有自己的域名,需要改成auto。 Customization 如果你喜欢折腾,你可以去自定义这个模板的 Code。 如果你可以理解 _include/ 和 _layouts/文件夹下的代码(这里是整个界面布局的地方),你就可以使用 Jekyll 使用的模版引擎 Liquid的语法直接修改/添加代码,来进行更有创意的自定义界面啦! Header Image 博客每页的标题底图是可以自己选的,看看几篇示例post你就知道如何设置了。 标题底图的选取完全是看个人的审美了。每一篇文章可以有不同的底图,你想放什么就放什么,最后宽度要够,大小不要太大,否则加载慢啊。 上传的图片最好先压缩,这里推荐 imageOptim 图片压缩软件,让你的博客起飞。 但是需要注意的是本模板的标题是白色的,所以背景色要设置为灰色或者黑色,总之深色系就对了。当然你还可以自定义修改字体颜色,总之,用github pages就是可以完全的个性定制自己的博客。 SEO Title 我的博客标题是 呐啥 ,但是我想要在搜索的时候显示 哦豁 ,这个就需要 SEO Title 来定义了。 其实这个 SEO Title 就是定义了 <head><title>标题</title></head> 这个里面的东西和多说分享的标题,可以自行修改的。 增加阅读时间和字数统计 注意:以下代码中出现的全角花括号要改成半角花括号。不要问我为什么,问就是这个影响我全局代码编译。 Displaying a post’s word count is rather common when creating a blog, but usually those techniques rely on JavaScript to work. The script reads the post’s text, counts the words and displays the result accordingly. That was the way I did things on this blog first as well, but then I set out to find a better way. Showing the word count Luckily Jekyll provides a handy liquid filter called number_of_words. So displaying the actual word count is as simple as that: {{ page.content | number_of_words }} While this works just nicely it’s not very solid. You might want to hide word counts on shorter posts, for example as they’re of little value in such posts. This is a little more complex as you can not directly use Liquid filters in a conditional block. Variables in Liquid In Liquid there are two ways to create variables. You can {% assign %} a variable and you can {% capture %} a variable. The difference might not be obvious, but it’s simple once you get it. Assigning a value to a variable means that you take any kind of data (e.g. a string, a number, a boolean) and Liquid knows that you want to access that exact data when you refer to this variable. An assigned variable is fixed, that means you can not use the value returned from other Liquid tags. {% assign awesome = true %} {% if awesome %} <p>Yay, awesome!</p> {% endif %} But what if you want to store a Liquid tags’s return value in a variable? That’s exactly what the {% capture %} block is for. Unlike assigned variables, captured variables can only hold strings — which will cause us some trouble later on. This is simply because Liquid tags return strings by default. {% capture value %} {{ page.title | upcase }} from {{ page.date | date: "%b %d, %y" }} {% endcapture %} As you can see in the above example, you can capture any number of strings into a variable, be it strings returned from a Liquid tag or fixed strings. Making the word count conditional Now that you know about {% assign %} and {% capture %} we can move on to store our word count in a variable. The question remains, do we assign the variable or do we capture it? It should be clear by now that we’ll have to capture the value as it’s returned from a Liquid tag. That gives us something like this: {% capture words %} {{ page.content | number_of_words }} {% endcapture %} Let’s say we considered posts that are shorter than 250 words not worthy of getting the word count. A good example for this would be ‘link list’-style post that consist of mostly a quote from the original article and a comment spanning a sentence or two. Ideally, this would be taken care of using a simple conditional block. {% if words > 250 %} {{ words }} {% endif %} But you’ll soon see that this won’t work as intended as Jekyll will throw you this error an error saying you’ve attempted to compare a string (the words) with a number (250), which is entirely true, and also, sadly, entirely not possible. There is, however, a simple workaround. {% capture words %} {{ page.content | number_of_words | minus: 250 }} {% endcapture %} {% unless words contains "-" %} {{ words | plus: 250 }} {% endunless %} You can use Liquid filters to substract your minimum number from the word count to see if it falls below 0. If it does it will contain a ‘-‘ at the beginning, which means the post is too short and won’t get the word number displayed. If our variable doesn’t contain a ‘-‘ we can simply add our minimum number back to the word count and display it. Quite simple, right? Customising the output Now that we finally have our word number along with the conditional to hide it from short posts we can move on to make the output a bit nicer. You do this using Liquid filters like append or prepend. For a complete list of available filters you can check Shopify’s Liquid for Designers guide. {{ words | plus: 250 | append: " words" }} The above snippet results in something like ‘There are 250 words in this post’. You can go crazy with filters, they offer lots of possibilities. Calculating the reading time You might have noticed that I display an estimated reading time on this blog instead of just a word count. Personally, I just think this is a more useful guideline. Doing this is as easy as putting the divided_by filter into our final word count construct. The number to divide by is arbitrary, but 180 is the avarage number of words a person reads per minute. {{ words | plus: 250 | divided_by: 180 | append: " minutes to read" }} Summing it up {% capture words %} {{ page.content | number_of_words | minus: 250 }} {% endcapture %} {% unless words contains "-" %} {{ words | plus: 250 | append: " words" }} {% endunless %} {% capture words %} {{ page.content | number_of_words | minus: 250 }} {% endcapture %} {% unless words contains "-" %} {{ words | plus: 250 | divided_by: 180 | append: " minute read" }} {% endunless %} 这是我用的参数 在/_include下新建read_time.html和word_count.html <!-- Add Read Time and word count, by chiya 2021.02.06--> {% capture words %} {{ content | number_of_words | minus: 0 }} {% endcapture %} {% unless words contains '-' %} {{ words | plus: 200 | divided_by: 100 | append: ' minute(s)' }} {% endunless %} <!-- Add Read Time and word count, chiya 2021.02.06--> {% capture words %} {{ page.content | number_of_words | minus: 10 }} {% endcapture %} {% unless words contains "-" %} {{ words | plus: 10 | append: " words" }} {% endunless %} 然后编辑./_layouts/post.html,大概在43行处。 <header class="intro-header" > <div class="header-mask"></div> <div class="container"> <div class="row"> <div class="col-lg-8 col-lg-offset-2 col-md-10 col-md-offset-1"> <div class="post-heading"> <div class="tags"> {% for tag in page.tags %} <a class="tag" href="{{ site.baseurl }}/tags/#{{ tag }}" title="{{ tag }}">{{ tag }}</a> {% endfor %} </div> <h1>{{ page.title }}</h1> {% comment %} always create a h2 for keeping the margin , Hux {% endcomment %} {% comment %} if page.subtitle {% endcomment %} <h2 class="subheading">{{ page.subtitle }}</h2> {% comment %} endif {% endcomment %} <!-- Add Read Time and word count, by chiya 2021.02.06--> <p class="meta"> <span>Posted by {% if page.author %}{{ page.author }}{% else %}{{ site.title }}{% endif %} on {{ page.date | date: "%B %-d, %Y" }}</span> <span>- {% include word_count.html %}, {% include read_time.html %} to read</span> </p> </div> </div> </div> </div> </header> 最终使用 如果配置好nginx和Jekyll的话 cd 文件夹 git clone 仓库地址 git pull origin master jekyll build -d /var/www/html/ 现在我单独写了个脚本用于自动拉取编译以及发布: #!/bin/bash cd /var/www/html/ rm -rf /var/www/html/blog/ cd /root/icing.fun git pull origin master jekyll build -s /root/icing.fun -d /var/www/html/blog/ 然后 chmod +x deploy.sh rvm cron setup crontab -e # 编辑 * * * * * /bin/bash /root/push_blog.sh 每分钟就能拉取一次编译发布。

Make Windows Great Again

补记 电源计划 卓越性能,启动! powercfg -duplicatescheme e9a42b02-d5df-448d-aa00-03f14749eb61 显示成功。但在电源计划里依然没有显示卓越性能。 此时需要修改注册表的CSEnabled值,但CSEnabled不见了(之前有的),所以应该在管理员模式下的 cmdlet 敲 reg add HKLM\System\CurrentControlSet\Control\Power /v PlatformAoAcOverride /t REG_DWORD /d 0 问了一下 Premier 事业部的同事,他们说目前据了解20H2取消了csenable更改来设置高性能,需要指令调用。 ==================================== 如何优雅地使用 Windows 作为开发环境。 如何使用 WSL 在 Windows 上优雅地安装 Linux 检视你电脑的版本 必须是运行 Windows 10 版本 2004 及更高版本(内部版本 19041 及更高版本)或 Windows 11 的电脑才能优雅。其他的版本可以参阅手动安装页,但是不优雅。 安装 WSL 优雅的命令 现在,可以使用单个命令安装运行 WSL 所需的一切内容。 在管理员模式下打开 PowerShell 或 Windows 命令提示符,方法是右键单击并选择“以管理员身份运行”,输入 wsl --install 命令,然后重启计算机。 wsl --install 此命令将启用运行 WSL 并安装 Linux 的 Ubuntu 发行版所需的功能。 (可以更改此默认发行版)。 如果你运行的是旧版,或只是不想使用 install 命令并希望获得分步指引,请参阅旧版 WSL 手动安装步骤。 首次启动新安装的 Linux 发行版时,将打开一个控制台窗口,要求你等待将文件解压缩并存储到计算机上。 未来的所有启动时间应不到一秒。 上述命令仅在完全未安装 WSL 时才有效,如果运行 wsl –install 并查看 WSL 帮助文本,请尝试运行 wsl –list –online 以查看可用发行版列表并运行 wsl –install -d 以安装发行版。 若要卸载 WSL,请参阅卸载旧版 WSL 或注销或卸载 Linux 发行版。 更改默认安装的 Linux 发行版 默认情况下,安装的 Linux 分发版为 Ubuntu。 可以使用 -d 标志进行更改。 若要更改安装的发行版,请输入:wsl --install -d <Distribution Name>。 将 <Distribution Name> 替换为要安装的发行版的名称。 若要查看可通过在线商店下载的可用 Linux 发行版列表,请输入:wsl --list --online 或 wsl -l -o。 若要在初始安装后安装其他 Linux 发行版,还可使用命令:wsl --install -d <Distribution Name>。 如果要通过 Linux/Bash 命令行(而不是通过 PowerShell 或命令提示符)安装其他发行版,必须在命令中使用 .exe:wsl.exe --install -d <Distribution Name> 或若要列出可用发行版,则使用:wsl.exe -l -o。 Ubuntu 不够你用是吧? 将版本从 WSL 1 升级到 WSL 2 使用 wsl --install 命令安装的新 Linux 安装将默认设置为 WSL 2。 wsl --set-version 命令可用于从 WSL 2 降级到 WSL 1,或将以前安装的 Linux 发行版从 WSL 1 更新到 WSL 2。 要查看 Linux 发行版是设置为 WSL 1 还是 WSL 2,请使用命令 wsl -l -v。 要更改版本,请使用 wsl --set-version <distro name> 2 命令将 <distro name> 替换为要更新的 Linux 发行版的名称。 例如,wsl --set-version Ubuntu-20.04 2 会将 Ubuntu 20.04 发行版设置为使用 WSL 2。 如果在 wsl --install 命令可用之前手动安装了 WSL,则可能还需要启用 WSL 2 所使用的虚拟机可选组件并安装内核包(如果尚未这样做)。 WSL 子系统访问宿主机的 Proxy 在 WSL 里面新建一个 setProxy.sh , 然后把这个复制进去: #!/bin/bash hostip=$(cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }') wslip=$(hostname -I | awk '{print $1}') port=7890 PROXY_HTTP="http://${hostip}:${port}" set_proxy(){ export http_proxy="${PROXY_HTTP}" export HTTP_PROXY="${PROXY_HTTP}" export https_proxy="${PROXY_HTTP}" export HTTPS_PROXY="${PROXY_HTTP}" git config --global http.proxy "${PROXY_HTTP}" git config --global https.proxy "${PROXY_HTTP}" # 设置APT代理 echo "Acquire::http::Proxy \"${PROXY_HTTP}\";" | sudo tee /etc/apt/apt.conf.d/95proxies > /dev/null echo "Acquire::https::Proxy \"${PROXY_HTTP}\";" | sudo tee -a /etc/apt/apt.conf.d/95proxies > /dev/null } unset_proxy(){ unset http_proxy unset HTTP_PROXY unset https_proxy unset HTTPS_PROXY git config --global --unset http.proxy git config --global --unset https.proxy # 移除APT代理设置 sudo rm /etc/apt/apt.conf.d/95proxies } test_setting(){ echo "Host ip:" ${hostip} echo "WSL ip:" ${wslip} echo "Current proxy:" $https_proxy } if [ "$1" = "set" ] then set_proxy elif [ "$1" = "unset" ] then unset_proxy elif [ "$1" = "test" ] then test_setting else echo "Unsupported arguments." fi 理论上是所有系统都可以这样做 然后 chmod +x setProxy.sh , 最后要设置的时候就 source ./setProxy.sh set 就行了。 PowerShell 美化 Scoop包管理器: Scoop 是 Windows 的命令行安装程序。使用 Scoop,可以为终端安装程序和插件。 Set-ExecutionPolicy RemoteSigned -Scope CurrentUser irm get.scoop.sh | iex scoop search - 搜索软件 scoop install - 安装软件 scoop info - 查看软件详细信息 scoop list - 查看已安装软件 scoop uninstall - 卸载软件,-p删除配置文件。 scoop update - 更新 scoop 本体和软件列表 scoop update - 更新指定软件 scoop update * - 更新所有已安装的软件 scoop checkup - 检查 scoop 的问题并给出解决问题的建议 scoop help - 查看命令列表 scoop help - 查看命令帮助说明* gsudo(提权) gsudo 是 sudo 在 Windows 的等价物,具有与原始 Unix/Linux sudo 相似的用户体验。允许在当前控制台窗口或新控制台窗口中以提升的权限运行命令,或提升当前 shell。 只需 gsudo 将(或sudo别名)添加到您的命令中,它就会以提升的方式运行。 ###GSUDO #安装: scoop install gsudo #导入配置文件内$PROFILE: # 添加以下这行 Import-Module (Get-Command 'gsudoModule.psd1').Source # 或者运行这行命令: Get-Command gsudoModule.psd1 | % { Write-Output "`nImport-Module `"$($_.Source)`"" | Add-Content $PROFILE } #在PowerShell中执行notepad $PROFILE,打开文件。 #在其中添加: Set-alias 'su' 'gsudo' Set-alias 'sudo' 'gsudo' .$PROFILE使配置生效 # gsudo如果用户选择启用缓存,则可以提升多次,仅显示一个 UAC 弹出窗口。 # 凭据缓存 使用 手动启动/停止缓存会话gsudo cache {on | off}。 使用 停止所有缓存会话gsudo -k。 可用的缓存模式: Disabled:每次提权都会显示一个 UAC 弹出窗口。 Explicit:(默认)每次提权都会显示一个 UAC 弹出窗口,除非缓存会话以gsudo cache on Auto:类似于 unix-sudo。第一个提权显示 UAC 弹出窗口并自动启动缓存会话。 使用更改缓存模式gsudo config CacheMode Disabled|Explicit|Auto Git 和 posh-git ###SCOOP安装GIT scoop bucket add main scoop install git #验证GIT安装: git --version #模块安装posh-git Install-Module posh-git -Scope AllUser -Force #notepad $PROFILE打开配置档案,添加以下: Import-Module posh-git #配置完成后,运行.$PROFILE,使得修改生效 # 克隆任何 GitHub 存储库,以测试在输入常用 git 命令时 posh-git的反映: git clone https://github.com/dahlbyk/posh-git cd posh-git PSReadline 语法定制 PSReadLine 是微软创建的一个模块,用于自定义 PowerShell 中的命令行编辑环境。它提供了大量的定制,可以改变命令行编辑器以多种方式呈现数据的方式。 # 安装 Install-Module PSReadLine # 配置文件: Import-Module PSReadLine fzf fzf 是一个用于命令行的模糊文件查找器。 这将为当前文件夹层次结构中的文件启用搜索机制,或者能够查看您之前使用的先前命令。 要使用 PSFzf,您必须先安装 fzf。 # 首先,需要通过SCOOP安装 scoop install fzf # 第二步,安装模块: Install-Module PSFzf # 配置文件: Import-Module PSFzf Set-PsFzfOption -PSReadLineChordProvider ‘Ctrl+f’ -PSReadLineChordReverseHistory ‘Ctrl+r’ ### #Ctrl+f您可以在当前文件夹和子文件夹中搜索文件。 #您可以通过Ctrl+r列表查看使用过的命令的历史记录。 z z 可让您根据cd命令历史记录在 PowerShell 中快速浏览文件系统。 z 也是经常使用的模块之一,它有助于快速导航到常用目录, Install-Module -Name z Neovim scoop install neovim Powershell 升级 iex "& { $(irm https://aka.ms/install-powershell.ps1) } -UseMSI" Git 一键推送 新建一个 git_push.ps1: # PowerShell Script to Perform Git Operations with User-Inputted Path # Prompt for the path to the Git repository $repoPath = Read-Host -Prompt "Enter the path to your git repository" # Change to the specified directory cd $repoPath # Update git git pull # Add all changes to staging git add --all # Prompt for a commit message $commitMessage = Read-Host -Prompt "Enter your commit message" # Commit the changes git commit -m "$commitMessage" # Push the changes to the remote repository git push # Back to the origin folder cd .. 倒叙之前因后果 11月月初俺爹斥巨资给我换了个笔记本电脑 可能有人要“素质三连”了 Surface Pro 9 不是有酷睿版吗?SQ3 版不是很垃圾吗?有这个钱为什么不买苹果? 首先就是全功能 Type-C 接口,两个接口可以提供视频,音频,数据传输,更重要的是可以充电。这不比原装的那个方便?带个小米67W就可以搞定手机和电脑的充电了。酷睿版就八行,只能拿那个笨笨的充电器充电。 5G 网络说实在真的很香,之前在学校办的移动的号,大流量卡,装这个上面,基本上是全天在线,很舒服。 之前去上海面试的时候,在高铁上就可以用电脑来办公,还能追追剧。 随身WiFi稍微麻烦了点,比如我现在也在用华为的。 再来就是这个屏幕了,这个屏幕香不香我不知道,主要是能触屏,MacBook那么多年也不能触屏。(估计有人就要骂我,说苹果触控板好用,我只能说,我不喜欢,我喜欢触屏) 说它是个大号平板也不为过,我现在就是拿着它在写这篇文章,很舒服。安卓平板根本打不过,如果说我要用安卓应用,Windows还有安卓子系统。 8cx Gen 3 跑分啥的对我来说确实不重要,抛开实际体验的跑分都是耍流氓。 实际体验下来没啥用不了的应用,转译慢就慢我还能摸摸鱼。Python脚本可以跑,我用的是 Anaconda 的环境,没啥问题。IDE用的 VSCode,有原生 ARM64 版本,也没啥问题。VS也能用,还能编译x64/x86的程序,也没啥问题。