Rust + MicroBit 按钮调试代码

#![no_main]
#![no_std]

use panic_halt;

use microbit::hal::nrf51::{interrupt, GPIOTE, UART0};
use microbit::hal::prelude::*;
use microbit::hal::serial;
use microbit::hal::serial::BAUD115200;

use cortex_m::interrupt::Mutex;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;

use core::cell::RefCell;
use core::fmt::Write;
use core::ops::DerefMut;

static GPIO: Mutex<RefCell<Option<GPIOTE>>> = Mutex::new(RefCell::new(None));
static TX: Mutex<RefCell<Option<serial::Tx<UART0>>>> = Mutex::new(RefCell::new(None));

#[entry]
fn main() -> ! {
    if let (Some(p), Some(mut cp)) = (microbit::Peripherals::take(), Peripherals::take()) {
        // 引入两个版本的外设
        cortex_m::interrupt::free(move |cs| {
            /* 开启外部设备的 GPIO 中断 */
            cp.NVIC.enable(microbit::Interrupt::GPIOTE);

            microbit::NVIC::unpend(microbit::Interrupt::GPIOTE);

            /* 切分 GPIO 口,方便使用 */
            let gpio = p.GPIO.split();

            /* 将 Button 的 IO 口作为输入 IO 口 */
            let _ = gpio.pin26.into_floating_input();
            let _ = gpio.pin17.into_floating_input();

            /* 当 GPIO 17 ( A 键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[0]
                .write(|w| unsafe { w.mode().event().psel().bits(17).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in0().set_bit());
            p.GPIOTE.events_in[0].write(|w| unsafe { w.bits(0) });

            /* 当 GPIO 26 (B键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[1]
                .write(|w| unsafe { w.mode().event().psel().bits(26).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in1().set_bit());
            p.GPIOTE.events_in[1].write(|w| unsafe { w.bits(0) });

            *GPIO.borrow(cs).borrow_mut() = Some(p.GPIOTE);

            /* 根据需要,设置 GPIO 口作为输入输出口 */
            let tx = gpio.pin24.into_push_pull_output().downgrade();
            let rx = gpio.pin25.into_floating_input().downgrade();

            /* 将准备的 GPIO 口作为串口来使用 */
            let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();

            let _ = write!(
                tx,
                "\n\rWelcome to the buttons demo. Press buttons A and/or B for some action.\n\r",
            );
            *TX.borrow(cs).borrow_mut() = Some(tx);
        });
    }

    loop {
        continue;
    }
}

// 定义一个中断(如果函数出现了错误,就会触发中断),当我从按钮按下接收到了一个中断,这个函数就会被调用。
#[interrupt]
fn GPIOTE() {
    /* 进入中断内部的内容 */
    cortex_m::interrupt::free(|cs| {
        if let (Some(gpiote), &mut Some(ref mut tx)) = (
            GPIO.borrow(cs).borrow().as_ref(),
            TX.borrow(cs).borrow_mut().deref_mut(),
        ) {
            let buttonapressed = gpiote.events_in[0].read().bits() != 0; // 识别出 A 键按下
            let buttonbpressed = gpiote.events_in[1].read().bits() != 0; // 识别出 B 键按下

            /* 将按钮打印到串口中 */
            let _ = write!(
                tx,
                "Button pressed {}\n\r",
                match (buttonapressed, buttonbpressed) {
                    (false, false) => "",
                    (true, false) => "A",
                    (false, true) => "B",
                    (true, true) => "A + B",
                }
            );

            /* 清空事件 */
            gpiote.events_in[0].write(|w| unsafe { w.bits(0) });
            gpiote.events_in[1].write(|w| unsafe { w.bits(0) });
        }
    });
}

微信开放社区抓取工具 CHANGELOG

描述

这个连接是我自己用的工具的更新 CHANGELOG。如果想要试用,可以私聊我获取压缩包。

需求

  • 定时刷新
  • 气泡提醒
  • macOS 版本

CHANGELOG

0.0.0.4

  • U 抽象出新的函数,优化程序结构
  • U 更新了数据更新的机制,应用程序回归前台以后会自动刷新应用程序

0.0.0.3

  • 完成数据抓取的能力

如何 Docker 化一个 Cli 工具

需求

我在看 Hexo 的 issue 时,看到了一个需求

Docker image to avoid the environment setting issue.

刚好,我自己有 Docker 的基础,就决定提交一个 PR ,解决这个问题。

核心实现

在开发这一部分的时候,一个最核心的问题是,你需要准备 2 个文件,一个是 DockerFile ,另一个是对应的 Bash Script。

原因在于

  • Docker File 用于打包基础环境,比如全局安装 Hexo
  • Bash Script 则是为了方便挂载本地的文件系统,开辟端口等(端口可以放在 Docker file 中,文件系统必须要现场挂载,因为你的目的是使用 Cli 管理本地文件,就一定要把文件挂载过去)

具体实现的思路是,Docker 镜像本身提供的是基础环境,将 CMD 设置为 Bash ,方便执行具体的命令。

而 Bash Script 则将需要执行的命令整体传递过去。

代码

Docker File

FROM node:10

RUN npm install -g hexo-cli

CMD ["/bin/bash"]

Bash Script

#!/bin/sh
docker run \
  --interactive --tty --rm \
  --volume "$PWD":/hexosite \
  --workdir /hexosite \
  -p 4000:4000 \
  bestony/hexojs:latest "$@"

总结

Docker 化 Cli 命令其实并不复杂,核心在于 CMD 与你的 Bash Script 的配合。

其他

你可以查看 https://github.com/hexojs/hexo/pull/3891 来学习到更多的内容。

Windows Terminal 的 WSL 配置

我在考虑将自己的笔记本从 macOS 转为 Windows,所以就开始研究 Windows 下我常用的软件的替代品,于是,便试了试 Windows Subsystem For Linux ,这一试,发现 WSL 的确不错,很好用,便下载了 Windows Terminal ,作为自己的新的开发环境。

在使用 Windows Terminal 的时候,我遇见了一个问题,我希望在 Windows Terminal 中添加一个新的配置文件,从而让我的 Windows Terminal 在启动的时候就自动进入 Linux 子系统,因此,在互联网上搜索了一些教程后,我将我自己的配置改成了这个样子的。

{
    "globals" : 
    {
        "alwaysShowTabs" : true,
        "copyOnSelect" : false,
        "defaultProfile" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
        "initialCols" : 120,
        "initialRows" : 30,
        "keybindings" : 
        [
            {
                "command" : "closePane",
                "keys" : 
                [
                    "ctrl+shift+w"
                ]
            }
        ],
        "requestedTheme" : "system",
        "showTabsInTitlebar" : true,
        "showTerminalTitleInTitlebar" : true,
        "wordDelimiters" : " ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502"
    },
    "profiles" : 
    [
        {
            "acrylicOpacity" : 0.5,
            "background" : "#012456",
            "closeOnExit" : true,
            "colorScheme" : "Campbell",
            "commandline" : "powershell.exe",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 12,
            "guid" : "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
            "name" : "Windows PowerShell",
            "padding" : "0, 0, 0, 0",
            "snapOnInput" : true,
            "startingDirectory" : "%USERPROFILE%",
            "useAcrylic" : false
        },
        {
            "acrylicOpacity" : 0.75,
            "closeOnExit" : true,
            "colorScheme" : "One Half Dark",
            "commandline" : "debian",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 11,
            "guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png",
            "name" : "Debian Path",
            "padding" : "0, 0, 0, 0",
            "startingDirectory" : "/home/bestony",
            "snapOnInput" : true,
            "useAcrylic" : true
        }
    ],
    "schemes" : 
    [
        {
            "background" : "#282C34",
            "black" : "#282C34",
            "blue" : "#61AFEF",
            "brightBlack" : "#5A6374",
            "brightBlue" : "#61AFEF",
            "brightCyan" : "#56B6C2",
            "brightGreen" : "#98C379",
            "brightPurple" : "#C678DD",
            "brightRed" : "#E06C75",
            "brightWhite" : "#DCDFE4",
            "brightYellow" : "#E5C07B",
            "cyan" : "#56B6C2",
            "foreground" : "#DCDFE4",
            "green" : "#98C379",
            "name" : "One Half Dark",
            "purple" : "#C678DD",
            "red" : "#E06C75",
            "white" : "#DCDFE4",
            "yellow" : "#E5C07B"
        }
    ]
}

这个配置项目中,需要注意的是 profiles.[1].commandline 你会发现,我设置的是 debian 这个和很多教程是不一样的,不少教程使用的是 wsl -d debian 这样的,但是实际上,如果你使用的是 wls -d debian ,你会发现,你配置的 startingDirectory 就会失效,但是如果你的启动命令是 debian, 不会影响你的 startingDirectory 的配置,非常舒服。

Reference

  • https://lwz322.github.io/2019/06/01/Terminal.html
  • https://github.com/microsoft/terminal/issues/1183

WebSocket 如何实现服务端向客户端发送消息?

我们都知道, Websocket 是一个双向的通讯方式,一般情况下,我们都是根据 Client 的情况返回信息,但是在一个更加健壮的系统,我们可能需要主动的向客户端发送消息。我试图在中文网络去搜索,查找相关信息,无果。因此便开始搜索英文世界中的内容。如今已经实现我想要的需求,便写一篇文章分享一下。

需求

需求是 Websocket 服务端作为中心控制服务器,会对外提供一个 RESTFul API,其他部件通过 RESTFul API 来链接 WebSocket 服务端发起请求,由 WebSocket 服务端进行设备相关的控制。

实现

首先,前提是我们的服务端和设备端是保持着 websocket 的长链接操作的,那么,我们在 RESTFul 中只要获取到这个链接,就可以发送消息了。

而 websocket 中,我们如果想要获取到一个特定的链接,就需要使用 websocket 的 socket.id 来完成我们的需求。这就要求我们提前将 socket.id 存储起来,这样当我们需要的时候,我们就可以直接拿 socket.id 来发送消息。

在 Socket.io 中,你需要使用 io.sockets.connected[socketID].emit(“greeting”, “Howdy, User 1!”); 来发送消息。

在 egg.js 中,则是 app.io.sockets.connected[socketID].emit(‘res’, ‘send From app’);

有了这个代码,你就可以实现在 RESTFul API 中向 Client 中发送消息了。

参考代码

说明

本例中使用的是 Socket.io + Egg.js 实现的

服务端代码

//config/config.default.js
config.io = {
    namespace: {
      '/': {
        connectionMiddleware: [ 'auth' ],
        packetMiddleware: [],
      },
    },
  };
// app/io/middleware/auth.js
'use strict';

module.exports = () => {
  return async (ctx, next) => {
    const { socket, app } = ctx;
    const id = socket.id; // 获取 Socket ID
    app.redis.set('pid', id); // 设置 Socket ID
    ctx.socket.emit('res', `Your id is ${id}`);
    await next();
  };
};
// app/controller/home.js
'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async send() {
    const { ctx, app } = this;
    const id = await app.redis.get('pid'); // 获取 ID
    app.io.sockets.connected[id].emit('res', 'send From app');// 发送数据
    ctx.body = 'ok';
  }
}

module.exports = HomeController;

参考文章

https://michaelheap.com/sending-messages-to-certain-clients-with-socket-io/

Homebrew 使用指北

对于习惯了使用命令来完成一切的程序员来说,安装软件这种小事,自然是能够用命令解决,就不用图形界面选择。但是在 Linux 中,我们有 yumaptdnfpkg等命令来完成软件的安装,macOS 却并未为我们提供一个好用的包管理器,帮助我们更好的使用 macOS。

好在,虽然官方没有提供,我们却可以使用 Homebrew 这一神器,来完成类似的工作,就如同 Homebrew 的 Slogan :“The missing package manager for macOS (or Linux)”

Homebrew

Homebrew 由开发者 Max Howell 开发,并基于 BSD 开源,是一个非常方便的包管理器工具。在早期, Homebrew 仅有 macOS 的版本,后续随着用户的增多,Homebrew 还提供了 Linux 的版本,帮助开发者在 Linux 同样使用 Homebrew 来配置环境。

Homebrew 的几个核心概念

在正式介绍 Homebrew 的使用之前,我先为你介绍一下 Homebrew 中的一些核心的概念,了解这些概念,就可以帮助你更好的去使用 Homebrew。

词汇含义
formula (e)安装包的描述文件,formulae 为复数
cellar安装好后所在的目录
keg具体某个包所在的目录,keg 是 cellar 的子目录
bottle预先编译好的包,不需要现场下载编译源码,速度会快很多;官方库中的包大多都是通过 bottle 方式安装
tap下载源,可以类比于 Linux 下的包管理器 repository
cask安装 macOS native 应用的扩展,你也可以理解为有图形化界面的应用。
bundle描述 Homebrew 依赖的扩展

其中,最关键的是 tap 、cask,我们在后续会经常用到。

Homebrew 常用操作

安装 Homebrew

在使用 Homebrew 之前,首先我们需要完成 Homebrew 的安装工作。Homebrew 的安装工作非常简单,只需要执行如下代码,就可以自动开始安装流程,后续根据提示操作即可。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装软件

当你完成了 Homebrew 以后,就可以使用 Homebrew 来完成软件的安装了,安装命令行软件的时候非常简单,只需要执行 brew install [软件名] 就可以安装软件了,比如说,我们要安装 wget,那么只需要执行 brew install wget 就可以了。

搜索软件

很多时候,我们不知道自己想要的软件是否有,或者说具体的名字是什么,这个时候你有两种方式来完成搜索

1. 使用命令搜索

在命令行中,你可以直接使用 brew search [关键词] 来进行搜索

命令行搜索软件

输入你想要的关键词,来搜索即可得到结果。

在搜索时应当遵循宁可少字,不能错字的原则来搜索。

2. 使用网页搜索

除了使用命令行搜索以外,你可以使用网页端的搜索工具来辅助你进行搜索。在 Homebrew 的官网,你可以找到 formulae 的链接,或者直接访问 https://formulae.brew.sh/ 来进行搜索。你只需要在界面的输入框中输入你要搜索的命令,然后就会出现对应的候选命令

搜索软件

选择其中你要使用的那个,点击就会进入到软件的介绍页面

查看软件介绍

你就可以看到 Homebrew 中的软件具体信息。

查看已经安装的包

如果你想要查看你都安装了哪些包,你可以执行 brew list 命令,来查看所有你已经安装的软件。

查看所有软件

更新一个已经安装的包

我们安装的软件并不会自动更新,因此,我们可以根据自己的需求,批量更新软件,或者更新单个软件。

你可以先使用 brew outdated 来查看所有有更新版本的软件。

查看需要更新的软件

然后使用 brew upgrade 来更新所有的软件,或者是使用 brew upgrade [软件名]来更新单个软件。

卸载某个已经安装的包

如果你想要卸载某个包,你可以执行 brew uninstall [软件名] 来卸载一个特定的软件,比如卸载 wget 是这样的。

卸载某个已经安装的包

查看包的信息

如果你想要查看某个特定软件的信息,你可以执行命令 brew info [软件名] 来查看该软件的详情。

查看包的信息

清理软件的旧版

Homebrew 用久了,会有一些历史版本的软件遗留在系统里,这个时候,你可以使用 brew cleanup 命令来清理系统中所有软件的历史版本,或者可以使用 brew cleanup [软件名]来清理特定软件的旧版。

清理软件的旧版

管理后台软件

诸如 Nginx、MySQL 等软件,都是有一些服务端软件在后台运行,如果你希望对这些软件进行管理,可以使用 brew services 命令来进行管理

  • brew services list: 查看所有服务
  • brew services run [服务名]: 单次运行某个服务
  • brew services start [服务名]: 运行某个服务,并设置开机自动运行。
  • brew services stop [服务名]:停止某个服务
  • brew services restart:重启某个服务。
管理后台软件

检查 Hombrew 环境

如果你的 Hombrew 没有办法正常的工作,你可以执行 brew doctor 来开启 Homebrew 自带的检查,从而确认有哪些问题,并进行修复。

检查 Hombrew 环境

更新 Homebrew

Homebrew 经常会在执行命令的时候触发更新,不过如果你想要主动检查更新,可以执行 brew update 来唤起 Homebrew 的更新。

添加一个新的 tap

homebrew 官方在安装的时候会有一些 tap 但是在使用时,依然会需要安装一些特殊的 tap ,这个时候,我们就要用到 tap 的命令来添加新的 tap.

在添加 tap 时,输入命令 brew tap [user/repo] ,就可以完成添加 tap 了

常用 tap

在使用 homebrew 时,我们一般会添加几个常用的 tap,来确保我们有足够的软件来安装。

1. Caskroom

Caskroom 是 Homebrew 下一个非常出名的 tap ,有了 caskroom,我们就可以安装一些有图形化界面的软件了,比如 VSCode、Typora 等软件。

使用起来也非常简单,最新版 Homebrew 中,你可以直接使用 brew cask install [软件名] 来安装特定的软件,homebrew 会自动安装 Caskroom。

2. homebrew-cask-fonts

程序员难免要安装一些代码字体,这样才能更好的写代码,Homebrew 也提供了方便我们安装字体的 tap。

在使用时,你需要先添加对应的 tap ,然后执行安装即可了,比如我们要安装 source code pro ,只需要执行如下命令。

brew tap homebrew/cask-fonts
brew cask install font-source-code-pro

使用技巧

切换国内的镜像源

Homebrew 默认使用的是国外的源,在下载时速度可能会比较慢。好在国内的清华大学和中科大提供了 Homebrew 的镜像源,我们可以很轻松的切换源,从而提升我们的下载速度。

使用中科大的镜像

执行如下命令,即可切换为中科大的镜像

cd "$(brew --repo)"
git remote set-url origin git://mirrors.ustc.edu.cn/brew.git
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin git://mirrors.ustc.edu.cn/homebrew-core.git

使用清华大学的镜像

执行如下命令,即可切换为清华大学的镜像

git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git

使用 Brewfile 完成环境迁移

设备永久了,我们的电脑中会有大量的软件,如果你需要迁移环境,重新安装会是一个大麻烦,好在 Homebrew 本身为我们提供了一个非常好用的环境迁移的工具 —— Homebrew Bundle

你首先需要在之前的电脑中执行 brew bundle dump 来完成当前环境的导出,导出完成后,你会得到一个 Brewfile

然后将 Brewfile 复制到新的电脑中,并执行 brew bundle 来开始安装的过程。

使用网页搜索 Caskroom 的软件

Brew Caskroom 并没有提供搜索的命令,不过我们可以借助一些网站来进行搜索,一个是 Homebrew 官方的 Caskrrom 页面:https://formulae.brew.sh/cask/

在这个页面,你可以看到所有被收录的页面,在命令行中输入对应的软件就可以安装了。

你也可以访问 http://macappstore.org/,在网站中输入你要安装的软件,点击搜索,在弹出的页面中,查看安装指南即可。

辅助软件

除了命令行,还有两款软件可以帮助我们更好的使用 Homebrew ,他们分别是 Cakebrew 和 launchrocket。

Cakebrew

Cakebrew 是 Homebrew 的 GUI 管理器,在 Cakebrew 中,你可以看到当前所有已经安装的软件,并可以在 Caskbrew 中对其他软件执行升级等操作。

你只需要执行 brew cask install cakebrew 就可以完成 Cakebrew 的安装。

安装完成后,在 LaunchPad 中打开即可。

launchrocket

launchrocket 可以用于管理 Homebrew 安装的服务,在使用时,你需要先添加对应的tap,然后再安装软件。

brew tap jimbojsb/launchrocket
brew cask install launchrocket

安装完成后,在 LaunchPad 中打开即可。

Reference

  • Homebrew 官网:https://brew.sh
  • Homebrew Github:https://github.com/Homebrew/brew
  • Homebrew 的 Manpage 说明书:https://docs.brew.sh/Manpage

macOS Init Guide

homebrew

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

homebrew source

cd "$(brew --repo)"
git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git

cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git

brew update
brew install caskroom/cask/brew-cask 

iTerm 2

brew cask install iterm2

zsh

brew install zsh zsh-completions

OhMyZsh

sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

zsh-sytax-highlighting

brew install zsh-syntax-highlighting
source /usr/local/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

powerline

# clone
git clone https://github.com/powerline/fonts.git --depth=1
# install
cd fonts
./install.sh
# clean-up a bit
cd ..
rm -rf fonts

sublime text 3

brew cask install sublime-text
ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl /usr/local/bin/subl

autojump

brew install autojump

然后将下面这段代码加入 shell 的配置文件中,比如.bashrc.zhsrc 中.

[ -f /usr/local/etc/profile.d/autojump.sh ] && . /usr/local/etc/profile.d/autojump.sh

nvm (Node.js 环境管理)

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.34.0/install.sh | bash
nvm install node
nvm use node

nrm(Node.js Mirro 管理)

npm install nrm -g --save
nrm use taobao

PyEnv(Python 环境管理)

curl https://pyenv.run | bash
Pyenv install 3.7.3
export PATH="/Users/bestony/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

rbEnv

brew install rbenv
eval "$(rbenv init -)"

常用软件

brew cask install 1password sublime-text google-chrome

《编译原理》1.1 习题

1.1.1 编译器和解释器之间的区别是什么?

编译器会将高级语言处理成机器语言后执行。而解释器则不做处理,分步执行。

1.1.2 编译器相对于解释器的优点是什么?

编译器处理成机器语言后执行效率更高。

1.1.3 在一个语言处理系统中,编译器产生汇编语言而不是机器语言的好处是什么?

汇编语言便于调试

补充:调试和输出

1.1.4 把一种高级语言翻译到另外一种高级语言的编译器称为源到源的翻译器,编译器使用 C 语言作为目标语言有什么好处?

C语言接近系统底层,同时又便于看懂,方便调试。

1.1.5 描述一下汇编器所要完成的一些任务。

将外部的文件和源码文件连接起来。