Hydrogen

Hydrogen 查看完整档案

苏州编辑格拉斯哥大学  |  Computer Science 编辑AISpeech  |  嵌入式工程师 编辑 iamwz.me 编辑
编辑

Write the code. Change the world!

个人动态

Hydrogen 发布了文章 · 2019-07-02

将路由器温度通过MQTT协议加入Homeassistant及Homekit

前言

最近入手了一台斐讯K3路由器,可是博通的芯片发热太严重,想要随时了解路由器的温度,于是自己动手实现了一组程序来将路由器温度通过MQTT加入Homeassistant及Homekit.

准备

  1. 已经获取root权限的K3路由器
  2. 部署好HomeAssistant的内网服务器
  3. 部署好Node.js的内网服务器

前置知识

  • 基本的Shell编程
  • 基本的HTTP及web服务器知识
  • 基本的MQTT原理
  • 基础JavaScript编程

软件结构

最早的想法是直接在路由器上通过Python获取数据及通过MQTT发送至HASS服务器,但是一个是opkg的源太慢了,另一个是不想给路由器增加额外负担(万一加了测温软件温度涨几度就尴尬了),最终采用了如下的软件结构

  • 路由器---> Node.js Server ---> Homeassistant

其中路由器到Node.js通过简单的HTTP,将温度数据通过JSON发送过来.
Node.js通过Express.js实现web服务器用于接受路由器数据,mqtt包实现mqtt通讯,发送给Homeassistant.

服务器端Shell程序

温度获取

首先第一步是获取路由器的温度数据:

  • CPU
cat /proc/dmu/temperature #CPU温度
wl -i eth1 phy_tempsense  #2.4GHz无线芯片温度
wl -i eth2 phy_tempsense  #5GHz无线芯片温度

运行结果如下:
clipboard.png

需要注意的是这里有一个坑,CPU温度的文件中又不符合UTF-8的字符,直接用curl发的话会导致错误,这里我们直接用cut进行处理,这里暂时不考虑温度为或3位的情况(事实上不太可能发生)
cat /proc/dmu/temperature|cut -c19-20

同理我们对另外两项数据也进行处理

wl -i eth1 phy_tempsense|cut -c0-2
wl -i eth2 phy_tempsense|cut -c0-2

发送温度数据

HTTP数据约定

GET Method
数据位于data参数下,内容封装于JSON中,格式如下
{"type":"data","CPU":61,"W24G":51,"W5G":65}

curl实现

这里我们通过curl来发送数据,这里是用GET方法,为了以后增加控制信息方便,数据包格式如下

{"type":"data","CPU":61,"W24G":51,"W5G":65}

经过url encode的结果为

%7B%22type%22:%22data%22,%22CPU%22:61,%22W24G%22:51,%22W5G%22:65%7D

我的Node.js服务器地址是192.168.2.103,端口3000所以我们的url是

url="http://192.168.2.103:3000/?data=%7B%22type%22:%22data%22,%22CPU%22:$cpu,%22W24G%22:$w24,%22W5G%22:$w5%7D"

增加无限循环后的完整程序

#!/bin/sh

echo " run.. "
while true
do
    cpu=$(cat /proc/dmu/temperature|cut -c19-20)
    w24=$(wl -i eth1 phy_tempsense|cut -c0-2)
    w5=$(wl -i eth2 phy_tempsense|cut -c0-2)
    url="http://192.168.2.103:3000/?data=%7B%22type%22:%22data%22,%22CPU%22:$cpu,%22W24G%22:$w24,%22W5G%22:$w5%7D"
    curl $url
    sleep 60    #设置1分钟的查询周期
    continue
done
echo " end.. "
为了让程序不断运行,推荐用screen来管理

Node端程序

HTTP服务器

这里我们基本使用了模版提供的功能,由于我们这里只对数据进行透明传输,所以JSON不需要反序列化

router.get('/', function(req, res, next) {
  var data = req.query.data
  res.render('index', { title: 'Express' });
});

MQTT客户端

MQTT数据约定

我们的数据分别放在三个Topic下

homeassistant/sensor/K3/CPU/state
homeassistant/sensor/K3/24G/state
homeassistant/sensor/K3/5G/state

MQTT实现

Node端程序为了方便,我们直接通过webstorm创建Express.js程序模版,我们的程序之间放在router/index.js
记得用npm安装mqtt包
npm install mqtt

首先我们需要初始化mqtt,填入你的MQTT服务器账号密码(如果未设置匿名)

var mqtt = require('mqtt');
var client = mqtt.connect('mqtt://192.168.2.103',{
    username: 'homeassistant',
    password: 'password',
    clientId: 'K3Server'
}); //连接到服务端
client.subscribe('presence');
注意,mqtt中客户端id不可重复,后来者将踢出原先的客户端

Node完整程序

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  var data = req.query.data
    client.publish('homeassistant/sensor/K3/CPU/state', data, { qos: 0, retain: true });
    client.publish('homeassistant/sensor/K3/24G/state', data, { qos: 0, retain: true });
    client.publish('homeassistant/sensor/K3/5G/state', data, { qos: 0, retain: true });
  res.render('index', { title: 'Express' });
});

var mqtt = require('mqtt');
var client = mqtt.connect('mqtt://192.168.2.103',{
    username: 'homeassistant',
    password: 'hello',
    clientId: 'K3Server'
}); //连接到服务端
client.subscribe('presence');

module.exports = router;

HomeAssistant配置

我们这里使用HA自带的MQTT代理,配置如下

# Sensors
sensor:
  - platform: mqtt
    name: 'K3_CPU'
    state_topic: 'homeassistant/sensor/K3/CPU/state'
    unit_of_measurement: '°C'
    value_template: '{{ value_json.CPU }}'
  
  - platform: mqtt
    name: 'K3_24'
    state_topic: 'homeassistant/sensor/K3/24G/state'
    unit_of_measurement: '°C'
    value_template: '{{ value_json.W24G }}'

  - platform: mqtt
    name: 'K3_5'
    state_topic: 'homeassistant/sensor/K3/5G/state'
    unit_of_measurement: '°C'
    value_template: '{{ value_json.W5G }}'

效果

clipboard.png

clipboard.png

查看原文

赞 8 收藏 7 评论 0

Hydrogen 赞了回答 · 2018-12-03

解决UItextfield怎么做成下划线样式的?输入的文本在下划线上面?

方法不止一种,可以用category,可以直接骗用户,可以...这里说一种继承,步骤如下:
1)继承UITextfield
2)重写子类中的- (void)drawRect:(CGRect)rect 如下

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
    CGContextFillRect(context, CGRectMake(0, CGRectGetHeight(self.frame) - 0.5, CGRectGetWidth(self.frame), 0.5));
}

3)TestCode 如下:

#import "CustomField.h"
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    CustomField *textField = [CustomField new];
    textField.frame = CGRectMake(0, 200, 100, 30);
    textField.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:textField];
}

4)效果图:
图片描述

关注 4 回答 1

Hydrogen 赞了回答 · 2018-11-18

解决用node做爬虫,循环模拟接口获取数据,async/await怎么做到同步操作,控制流程?

可以使用 Node8 的 util.promisify,或者 Bluebird 等把 Node 回调形式的函数改成 Promise 风格的函数,然后就可以使用 async/await 来写代码。

代码本身还是异步调用,只是写法看起来像是同步的。所以在写的时候还是要注意流程结构,尤其是在写循环的时候。代的代码太长,所以我写个小例子来说明

async function remoteCall() {
    // do something
}

list = [];  // 假设是很多数据


async function process() {
    // 这种写法必须要一个 remoteCall 完成之后才进行另一个
    for (let i = 0; i < list.length; i++) {
        await remoteCall();
    }

    doAfter();
}

async function process2() {
    // 这种写法没法 await
    list.forEach(function(t) {
        remoteCall();
    });
}

async function process3() {
    // 这种写法 doAfter 一早就会执行
    list.forEach(async function(t) {
        await remoteCall();
    });

    // 它可能会在 remoteCall() 之前
    doAfter();
}

async function process4() {
    // 这种写法必须要全部 remoteCall 成功才能进行到 doAfter
    // remoteCall返回的 promise 如果 reject 会抛异常
    var promises = list.map(t => remoteCall());
    await Promise.all(promises);
    doAfter();
}

关注 4 回答 4

Hydrogen 赞了回答 · 2018-11-18

解决用node做爬虫,循环模拟接口获取数据,async/await怎么做到同步操作,控制流程?

可以使用 Node8 的 util.promisify,或者 Bluebird 等把 Node 回调形式的函数改成 Promise 风格的函数,然后就可以使用 async/await 来写代码。

代码本身还是异步调用,只是写法看起来像是同步的。所以在写的时候还是要注意流程结构,尤其是在写循环的时候。代的代码太长,所以我写个小例子来说明

async function remoteCall() {
    // do something
}

list = [];  // 假设是很多数据


async function process() {
    // 这种写法必须要一个 remoteCall 完成之后才进行另一个
    for (let i = 0; i < list.length; i++) {
        await remoteCall();
    }

    doAfter();
}

async function process2() {
    // 这种写法没法 await
    list.forEach(function(t) {
        remoteCall();
    });
}

async function process3() {
    // 这种写法 doAfter 一早就会执行
    list.forEach(async function(t) {
        await remoteCall();
    });

    // 它可能会在 remoteCall() 之前
    doAfter();
}

async function process4() {
    // 这种写法必须要全部 remoteCall 成功才能进行到 doAfter
    // remoteCall返回的 promise 如果 reject 会抛异常
    var promises = list.map(t => remoteCall());
    await Promise.all(promises);
    doAfter();
}

关注 4 回答 4

Hydrogen 赞了回答 · 2018-11-18

解决用node做爬虫,循环模拟接口获取数据,async/await怎么做到同步操作,控制流程?

可以使用 Node8 的 util.promisify,或者 Bluebird 等把 Node 回调形式的函数改成 Promise 风格的函数,然后就可以使用 async/await 来写代码。

代码本身还是异步调用,只是写法看起来像是同步的。所以在写的时候还是要注意流程结构,尤其是在写循环的时候。代的代码太长,所以我写个小例子来说明

async function remoteCall() {
    // do something
}

list = [];  // 假设是很多数据


async function process() {
    // 这种写法必须要一个 remoteCall 完成之后才进行另一个
    for (let i = 0; i < list.length; i++) {
        await remoteCall();
    }

    doAfter();
}

async function process2() {
    // 这种写法没法 await
    list.forEach(function(t) {
        remoteCall();
    });
}

async function process3() {
    // 这种写法 doAfter 一早就会执行
    list.forEach(async function(t) {
        await remoteCall();
    });

    // 它可能会在 remoteCall() 之前
    doAfter();
}

async function process4() {
    // 这种写法必须要全部 remoteCall 成功才能进行到 doAfter
    // remoteCall返回的 promise 如果 reject 会抛异常
    var promises = list.map(t => remoteCall());
    await Promise.all(promises);
    doAfter();
}

关注 4 回答 4

Hydrogen 赞了文章 · 2018-11-12

zsh+on-my-zsh配置教程指南(程序员必备)【已备份】

本文以CentOS 7/Mac 为例,介绍zsh的配置使用教程。

准备

查看当前环境shell

echo $SHELL

<!-- more -->

查看系统自带哪些shell

cat /etc/shells

安装zsh

yum install zsh # CentOS
brew install zsh # mac安装

zsh设置为默认shell

chsh -s /bin/zsh # CentOS
# Mac如下
# 在 /etc/shells 文件中加入如下一行
/usr/local/bin/zsh
# 接着运行
chsh -s /usr/local/bin/zsh

可以通过echo $SHELL查看当前默认的shell,如果没有改为/bin/zsh,那么需要重启shell。

oh-my-zsh

配置zsh是一件麻烦的事儿,爱折腾的程序猿怎么可能忍受?!于是,oh-my-zsh出现了,有了这个东东,zsh配置起来就方便多了!

安装oh-my-zsh

有若干安装方式,介绍三种:
1.自动安装

wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | sh

2.手动安装

git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

3.真-手动安装

  • oh-my-zsh的github主页,手动将zip包下载下来。
  • 将zip包解压,拷贝至~/.oh-my-zsh目录。此处省略拷贝的操作步骤。
  • 执行cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

三选一即可,适合各种环境下的安装,然后需要source ~/.zshrc将配置生效。以下修改了.zshrc文件之后,都执行一下这个命令。

zsh主题

通过如下命令可以查看可用的Theme

# ls ~/.oh-my-zsh/themes

如何修改zsh主题呢?
编辑~/.zshrc文件,将ZSH_THEME="candy",即将主题修改为candy。我采用的steeef

zsh扩展

~/.zshrc中找到plugins关键字,就可以自定义启用的插件了,系统默认加载git

git插件

命令内容可以参考cat ~/.oh-my-zsh/plugins/git/git.plugin.zsh

常用的:

gapa    git add --patch
gc!    git commit -v --amend
gcl    git clone --recursive
gclean    git reset --hard && git clean -dfx
gcm    git checkout master
gcmsg    git commit -m
gco    git checkout
gd    git diff
gdca    git diff --cached
gp    git push
grbc    git rebase --continue
gst    git status
gup    git pull --rebase

完整列表:https://github.com/robbyrussell/oh-my-zsh/wiki/Plugin:git

extract

解压文件用的,所有的压缩文件,都可以直接x filename,不用记忆参数

当然,如果你想要用tar命令,可以使用tar -tab键,zsh会列出参数的含义。

autojump

按照官方文档介绍,需要使用如下命令安装,而不是一些博客中的介绍:

yum install autojump-zsh # CentOS
brew install autojump # Mac

CentOS安装好之后,需要在~/.zshrc中配置一下,除了在plugins中增加autojump之外,还需要添加一行:

[[ -s ~/.autojump/etc/profile.d/autojump.sh ]] && . ~/.autojump/etc/profile.d/autojump.sh

安装好之后,记得source ~/.zshrc,然后你就可以通过j+目录名快速进行目录跳转。支持目录名的模糊匹配和自动补全。

  • j -stat:可以查看历史路径库

zsh-autosuggestions

zsh-autosuggestions

git clone git://github.com/zsh-users/zsh-autosuggestions $ZSH_CUSTOM/plugins/zsh-autosuggestions

~/.zshrc 中配置

plugins=(其他的插件 zsh-autosuggestions)

因为箭头不太方便,在.zshrc中自定义补全快捷键为逗号,但是又一次遇到了需要输入逗号的情况,所以,并不太推荐如下修改:

bindkey ',' autosuggest-accept

zsh-syntax-highlighting

zsh-syntax-highlighting

git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting

~/.zshrc文件中配置:

plugins=(其他的插件 zsh-syntax-highlighting)

git-open

git-open插件可以在你git项目下打开远程仓库浏览项目。

git clone https://github.com/paulirish/git-open.git $ZSH_CUSTOM/plugins/git-open

bat

bat 代替 cat
cat 某个文件,可以在终端直接输出文件内容,bat 相比 cat 增加了行号和颜色高亮 👍

brew install bat

常用快捷键

  • 命令历史记录

    • 一旦在 shell 敲入正确命令并能执行后,shell 就会存储你所敲入命令的历史记录(存放在~/.zsh_history 文件中),方便再次运行之前的命令。可以按方向键↑和↓来查看之前执行过的命令
    • 可以用 r来执行上一条命令
    • 使用 ctrl-r 来搜索命令历史记录
  • 命令别名

    • 可以简化命令输入,在 .zshrc 中添加 alias shortcut='this is the origin command' 一行就相当于添加了别名
    • 在命令行中输入 alias 可以查看所有的命令别名

使用技巧

  • 连按两次Tab会列出所有的补全列表并直接开始选择,补全项可以使用 ctrl+n/p/f/b上下左右切换
  • 智能跳转,安装了 autojump 之后,zsh 会自动记录你访问过的目录,通过 j 目录名 可以直接进行目录跳转,而且目录名支持模糊匹配和自动补全,例如你访问过 hadoop-1.0.0 目录,输入j hado 即可正确跳转。j --stat 可以看你的历史路径库。
  • 命令选项补全。在zsh中只需要键入 tar -<tab> 就会列出所有的选项和帮助说明
  • 在当前目录下输入 .. 或 ... ,或直接输入当前目录名都可以跳转,你甚至不再需要输入 cd 命令了。在你知道路径的情况下,比如 /usr/local/bin 你可以输入 cd /u/l/b 然后按进行补全快速输入
  • 目录浏览和跳转:输入 d,即可列出你在这个会话里访问的目录列表,输入列表前的序号,即可直接跳转。
  • 命令参数补全。键入 kill <tab> 就会列出所有的进程名和对应的进程号
  • 更智能的历史命令。在用或者方向上键查找历史命令时,zsh支持限制查找。比如,输入ls,然后再按方向上键,则只会查找用过的ls命令。而此时使用则会仍然按之前的方式查找,忽略 ls
  • 多个终端会话共享历史记录
  • 通配符搜索:ls -l **/*.sh,可以递归显示当前目录下的 shell 文件,文件少时可以代替 find。使用 **/ 来递归搜索
  • 扩展环境变量,输入环境变量然后按 就可以转换成表达的值
  • 在 .zshrc 中添加 setopt HIST_IGNORE_DUPS 可以消除重复记录,也可以利用 sort -t ";" -k 2 -u ~/.zsh_history | sort -o ~/.zsh_history 手动清除

最后

参考

Linux

Mac

查看原文

赞 62 收藏 38 评论 2

Hydrogen 发布了文章 · 2018-11-01

Android模拟器访问本机web app

今天在给自己的App加一个socketio的通讯,本地Node client可以成功连接,Android模拟器不可以,突然想到Android模拟器中的localhost是模拟器本机而不是开发的电脑,把它自己作为了localhost,也就是说,代码中使用localhost或者127.0.0.1来访问,都是访问模拟器本身。
如果你想在模拟器simulator上面访问你的电脑,可以使用android内置的IP 10.0.2.2 ,模拟器对你的电脑做了IP映射,用这个IP就可以访问到本机了

查看原文

赞 1 收藏 1 评论 1

Hydrogen 发布了文章 · 2018-10-26

解决Xcode10不支持libstdc++的问题

Apple在iOS12中取消了对stdlic++的支持
临时的解决方案是将老版本的Xcode中的文件copy到新版Xcode的目录中

模拟器
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/lib/
需要说明的是,iOS12模拟器即使添加了这个文件仍然不支持libstdc++,需要低于iOS12版本的模拟器才能正常使用

真机
/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/lib/

查看原文

赞 1 收藏 1 评论 0

Hydrogen 发布了文章 · 2018-10-16

android gradle 3.0.0 中依赖指令implementation、api 的区别

AndroidStudio升级到3.0之后,gradle版本也随之升级到了3.0.0版本。

classpath 'com.android.tools.build:gradle:3.0.0'
1
在新建一个Android工程的时候,build.gradle中的依赖默认为implementation,而不是之前的compile。另外,gradle 3.0.0版本以上,还有依赖指令api。本文主要介绍下implementation和api的区别。

新建工程默认生成的app的build.gradle文件中的依赖:

dependencies {

implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

}
1
2
3
4
5
6
7
8
api 指令

完全等同于compile指令,没区别,你将所有的compile改成api,完全没有错。

implementation指令

这个指令的特点就是,对于使用了该命令编译的依赖,对该项目有依赖的项目将无法访问到使用该命令编译的依赖中的任何程序,也就是将该依赖隐藏在内部,而不对外部公开。

简单的说,就是使用implementation指令的依赖不会传递。例如,有一个module为testsdk,testsdk依赖于gson:

implementation 'com.google.code.gson:gson:2.8.2'
1
这时候,在testsdk里边的java代码是可以访问gson的。

另一个module为app,app依赖于testsdk:

implementation project(':testsdk')
1
这时候,因为testsdk使用的是implementation 指令来依赖gson,所以app里边不能引用gson。

但是,如果testsdk使用的是api来引用gson:

api 'com.google.code.gson:gson:2.8.2'
1
则与gradle3.0.0之前的compile指令的效果完全一样,app的module也可以引用gson。这就是api和implementation的区别。

建议

在Google IO 相关话题的中提到了一个建议,就是依赖首先应该设置为implementation的,如果没有错,那就用implementation,如果有错,那么使用api指令。使用implementation会使编译速度有所增快。

参考:

http://blog.csdn.net/soslinke...

https://zhuanlan.zhihu.com/p/...

https://stackoverflow.com/que...

http://blog.csdn.net/JF_1994/...

查看原文

赞 3 收藏 0 评论 0

Hydrogen 关注了用户 · 2017-10-24

unko @unko

关注 2

认证与成就

  • 获得 102 次点赞
  • 获得 34 枚徽章 获得 1 枚金徽章, 获得 7 枚银徽章, 获得 26 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • CSVParser

    A swift package for read and write CSV file

注册于 2015-10-11
个人主页被 3.3k 人浏览