胡骊 发布的文章

从G1时代开始,就了解到因为Android使用Linux内核,可以利用chroot运行大量Linux发行版。但是由于当时ARM CPU性能低下及内存不足,一般只能使用Terminal字符界面,或者ssh过去。然后升级过设备,并装上了“XServer XSDL”(Android上的Xserver)用来体验图形界面,但是手机屏幕太小,一弹出虚拟键盘就基本把桌面挡住了。后来想起X Window是基于客户端/服务器模式的,应该可以用PC电脑之类提供Xserver,显示手机上的Linux图形界面。最后终于弄明白了配置,出来的效果还是不错,至少可以用浏览器流畅播放视频了。

所需设备
S机,用于提供X Server的设备,最好是屏幕比较大的PC电脑(台式机或笔记本)。系统最好是Linux,装上X11。后来发现Windows也可以,因为有Xming。当然,Android也是可以,因为有XServer XSDL。这里只记录Linux的。

A机,Android设备(手机或平板),运行Linux发行版的X Client。Android上有很多装Linux的应用了,这里推荐Linux Deploy,因为这是github.com上的开源项目。装Linux的步骤不详述,这个应用已经做得很好了。

网络设置
只要两个设备在同一局域网内就可以了。下面列出几种方式:
1)使用有线/无线路由器组建局域网。路由器是性能瓶颈,特别是A机无线连接到路由。
2)A机分享无线网络,S机连过去。缺点是只能利用A机的移动网络上网,费钱……
3)S机分享无线网络,A机连过去。S机需要装个无线网卡,并成为性能瓶颈。
4)A机开启开发者模式,通过USB线连接S机。S机利用ADB命令的forward功能映射端口。ADB命令成为性能瓶颈。
5)A机分享有线网络,通过USB线连接S机。这个方案性能最佳,且使用设备最小,又不影响A机的网络。但A机可能会出现发热的情况。

X Server设置
这一步花了很多时间。理解后就是S机上的一个命令和一个配置文件,即如何启动X Server和设置验证。相关原理可查Google,这里设置X Server为:1.0(从0开始算,第2个服务),默认使用6001端口。
1)利用xhost设置可访问X Server的客户端IP。在S机上修改配置文件/etc/X1.hosts,若不存在则新建。把A机的IP地址填进去,并保存。
2)运行以下命令启动X Server:

sudo Xorg :1.0 -listen tcp

X Client设置
A机上需要装好桌面环境。推荐LXDE吧,轻量。启动前,连好网络,在Linux Deploy上设置:

图形界面 -> 勾选启用
图形子系统 -> 选X11
图形界面设置 -> 显示编号 -> 1.0
           -> X服务器地址 -> 填写S机的IP地址
           -> XServer XSDL -> 不要勾选
桌面环境 -> 选LXDE

然后启动Linux即可(实在太方便了)。对应的命令就是:

export DISPLAY=S机_IP:1.0
startlxde

总结
A机的性能越好,并且S机的性能越差,这个方案的实用性就越高。比起微软的“Continue on PC”,三星的DeX,Superbook等方案,便宜很多,并把旧电脑利用起来。使用过程中,不会影响A机的来电、通知等日常用途。但是目前来看,实用性确实不高。后面看看能不能用Raspberry Pi Zero + 显示屏 + 大容量移动电源来作为S机,以提高便携性。

CFO离职,捡了个烂电脑回来(CPU是i3-4000M,第4代酷睿,内存4G)。比10年前的酷睿T2450好太多了,而且是64位系统。前段时间有新闻报道说,Google的内部系统Goobuntu,准备抛弃Ubuntu,直接基于Debian。于是也想试试直接装Debian来用。其实用习惯了Lubuntu,只要桌面也装LXDE,应该用起来没什么区别。
第一次装Debian,发现居然连下载哪个ISO都不知道。一开始下个live CD的iso文件,发 现虽然启动界面有选项可以安装系统,但是按抓给你过程要检查CD-ROM。没用光盘的话,不能继续进行安装。后来才明白要用netinst的iso。

iso文件制作U盘启动盘很简单,只要两行命令。其中sdX是U盘的设备文件名。如果U盘已挂载,要先卸载,再执行

$ sudo cp debian-9.4.0-amd64-netinst.iso /dev/sdX
$ sudo sync

后面的安装步骤基本按着提示一步一步进行。有个文章写得挺好的,可以参考:
如何安装 Debian 9?
http://scottming.com/2017/08/06/how_to_install_debian9/

有两个步骤需要说明一下:
第一,需要找无线网卡的firmware,放在另一个U盘,再插到电脑上。安装程序会自动设别并安装。这是为了后面安装软件时连上网络。或者可以使用有线连接吧。

第二,到了分区那一步,不能按默认的整个硬盘分区。因为要保留原来的Windows系统,所以只能选择手动操作。而且第一次搞LVM分区,完全不懂操作。后来参考了这个文章才能顺利搞掂:
ubuntu 12.10 安装 LVM分区(图文)
http://blog.sina.com.cn/s/blog_56a70c0401018dki.html

简单来说,要注意几点:
1)由于GRUB Legacy不支持LVM,所以无法在LVM上创建/boot分区。也就是除了/boot分区,其它分区都可以在LVM上创建。
2)先把分区设置为LVM分区,再点LVM分区管理的选项进行详细设置。
3)LVM分区管理,先创建逻辑卷组,再创建逻辑卷,多个逻辑卷就等于是多个分区。
4)最后把逻辑卷设置对应的挂载点。

最后总算是顺利安装完毕。

后记
用了一段时间后,终于明白Ubuntu的存在价值了。Debian 9即使装上LXDE,还是有一些不顺手,例如屏幕亮度调节。后来还是换上Lubuntu。

当年曾入手Chrome第一代,折腾过后,还是放弃了。原因有两个:一要联网才能用,二要能访问Google。相关的资料整理如下:

刷机
刷机,为了获取root权限,也为了获取更多的可能性(最后的结论是,然并卵…)。相关的教程都在:

[ROM] [44433.001 - 2015-11-15] Eureka-ROM - Based on OTA 44433 by Team-Eureka
http://forum.xda-developers.com/showthread.php?t=2578653

记得当时还入手了Arduino Nano(好像吧),用于破解。

开发
只是好奇查了相关的开发资料,但一直没深入。

GoogleCast 開發者官網
https://developers.google.com/cast/

A simple Hello World Sender/Receiver for Chromecast
https://github.com/pjjanak/chromecast-hello-world

然后,就酱……

搞了几天,终于弄懂了自建Git服务。正如绝大多数的教程所说,基于ssh来部署git服务,是最简单的。

1)服务器和客户端都安装Git。

sudo apt-get update
sudo apt-get install git

2)服务器上建立Git仓库。我比较懒,就用现有的用户,比如用户名为:user。用user登录后,执行命令。git init --bare是用于建立裸仓。执行成功后,会建立project.git文件夹,确保user对该文件夹有读写权限。

mkdir /path/git
cd /path/git
git init --bare project.git

3)客户端clone项目。ssh://是指,基于ssh的git服务。user就是用户名。server_ip就是服务器ip,也可以是域名。22是ssh服务的端口,根据部署的ssh服务而定。/path/git/project.git就是前面建立的项目路径。

git clone ssh://user@server_ip:22/path/git/project.git

然后就可以愉快地各种Git操作了。

至于后面增加安全性,可以新增一个git用户,设置其不能登录shell,并采用证书登录等等,可以参考网上的教程。例如:
廖雪峰的官方网站:搭建Git服务器

手上有个远古时代的上网本,华硕(ASUS) EeePC 900。删掉原来的Windows XP,装上Lubuntu 16.10。原电池废了,淘宝换了个新的,续航能达到5小时(大概吧)。但是一般用来远程ssh到raspberry pi,没必要开启图形界面(特别是发现用screen可以同一界面打开多个shell)。需要解决的问题是中文的显示和输入。Google过后,最终的解决方案是fbterm(完美显示中文) + fcitx-fbterm(实现中文输入)。

fbterm的安装参考这个(英文):
https://fcitx-im.org/wiki/Fbterm

中文版本的完整安装教程,参考这个:
http://ikuduku.com/blog/fbterm-display-and-input-Chinese-in-tty

关于中文输入法,我选择google拼音(fcitx-googlepinyin)。记录一下相关操作:

1)安装

#安装相关软件
sudo apt-get install fbterm fcitx fcitx-fbterm fcitx-googlepinyin fcitx-configtool
#让fbterm可以设置快捷键,主要是切换输入法
sudo setcap 'cap_sys_tty_config+ep' /usr/bin/fbterm
#把当前用户加入到video组
sudo gpasswd -a <your username> video

2)编辑当前用户的配置

vi ~/.fbtermrc

修改如下:

#设置字体
font-name=Mono
#设置字体大小
font-size=14
#设置输入法
input-method=fcitx-fbterm

3)设置开机启动

#设置开机进入shell界面,这是Ubuntu 16.04及以后版本的设置方法
sudo systemctl set-default multi-user.target
#设置进入图形界面的命令,然后用`sudo startx`命令即可进入图形界面
sudo sh -c "echo \#\!/bin/sh > /usr/local/bin/startx"
sudo sh -c "echo systemctl start lightdm >> /usr/local/bin/startx"
sudo chmod +x /usr/local/bin/startx
#设置进入shell后自动启动fbterm,而图形界面的shell则不会启动fbterm
printf '\nif [ $TERM = 'linux' ]; then\n  fcitx-fbterm-helper -l\nfi' >> ~/.bashrc

大概就这样了。使用过程中,还是有一点不习惯。例如快捷键的变换,Ctrl + Alt + F1 ~ F6 不能切换tty,而是切换字符编码,使用 Ctrl + Alt + 1 ~ 0 可以切换10个窗口。

一般一个月一次整理孩子的照片。但是老婆的手机和单反拍出来的照片,文件名都不统一。要统一采用拍照时间做为文件名的话,比较麻烦。本来想用Python3写个脚本处理的,但是官方没有提供获取EXIF信息的方法。找打其它包,也不怎么好用。但是今晚发现原来有个shell命令挺好用的,就是 EXIF。

于是就写了个shell脚本,如下:

#!/bin/bash

path=/home/fox/pics

i=$(ls $path/*.JPG)

for j in $i
do
  d=$(exif -t "DateTime" $j | grep Value)
  d=${d/  Value: /IMG_}
  d=${d//:/}
  d=${d// /_}
  $(mv $j ${d}.JPG)
done

这里采用的统一文件名格式是IMG_yyyyMMdd_HHmmss.jpg。由于采用字符串替换的语法,所以只能用bash运行。

某日发现了提供免费SSL证书的网站——www.sslforfree.com,立马手动申请了个证书来玩。部署以后,非常感动,重点是Chrome和FireFox都支持!曾在某知名网站申请过免费证书,总是申请失败。心灰意冷,本来准备买个廉价的付费版,幸好发现这网站!

免费虽好,但有效期只有3个月,而且到期后只能手动更新证书。不过找到提供该免费证书的网站——Let’s Encrypt(https://letsencrypt.org),以及该网站提供的自动更新证书的开源软件certbot(https://certbot.eff.org)。certbot首页就有安装教程,对应不同HTTP服务和操作系统。安装完毕后,执行以下命令,就可以获取证书了

#验证域名并获取SSL证书
sudo certbot certonly --webroot -w /var/www/example -d example.com -d www.example.com

最后,设置个自动更新,就安枕无忧了。

PS.既然有了免费的证书,何不部署个HTTP 2.0?

简单玩过NodeMCU之后,想获取更多的数据,就是多加几个传感器。但是NodeMCU只有一个ADC接口,于是找到利用Arduino与ESP8266结合的解决方案。Arduino的不同版本的接口数量不同,而手头上的Pro Micro就有4个ADC了,勉强够用。ESP8266就是NodeMCU的核心,相比NodeMCU更少接口,但体积更小,价格更低。于是入手了两个ESP8266来折腾。

First of All,刷固件。

刷固件的官方教程
Loading new firmware onto an ESP8266
http://www.esp8266.com/wiki/doku.php?id=loading_firmware

最新的固件下载
[最新发布]ESP8266 Resources
http://espressif.com/en/products/hardware/esp8266ex/resources

官方入门教程
Getting Started with ESP8266
http://www.esp8266.com/wiki/doku.php?id=getting-started-with-the-esp8266#got_an_esp-nn_board_in_the_mail_now_what

大概就这些了。话说,计划中的植物环境数据收集平台升级版,都拖好久了……

说起物联网,最早接触的是Arduino。可是入手Arduino Mega后,一直没做出什么好玩的东西,以致Mega丢在角落里好几年。近来在部门经理的怂恿下,入手了NodeMCU,做了个检测盆栽的土壤湿度。本来想在Github开个项目,但是这第一版太简单了,就随便记录一下。

NodeMCU的特点,在于自带WiFi(还可以使用AP模式),而且价格便宜。当然,缺点也在WiFi,比较耗电。为了省电,可以设置网卡的硬件参数(wifi.setphymode()),关闭WiFi(wifi.sleeptype()),甚至是整块板进入深度睡眠模式(node.dsleep(), rtctime.dsleep())。其开源、基于Lua的开发简单、尺寸迷你等优点,也是其好玩之处。

这个项目很简单,就是NodeMCU连个土壤湿度检测模块,定时(10分钟)获取一次数据,并上传到服务器。服务器是采用树梅派,用Python3,利用Flask框架,写了个HTTP服务,用于保存提交的数据,并做数据展示。数据都保存在MySQL数据库。

NodeMCU上的init.lua

print("\n")
print("ESP8266 Started")

dsleepTime = 630000000
ssid = "my-wifi"
password = "123456"
serverIp = "192.168.1.1"
serverPort = 8080

wifi.setphymode(wifi.PHYMODE_N)
wifi.setmode(wifi.STATION)
wifi.sta.config(ssid, password)
tmr.alarm(0,1000,1,function() 
    curIp = wifi.sta.getip()
    if curIp ~= nil then
        print("Current IP:"..curIp)
        tmr.stop(0)
        sendData(readValue(0, 5))
    end
end)

--发送数据到服务器    
function sendData(value)
    print("sendData start")
    conn = net.createConnection(net.TCP, false)
    conn:on("receive", function(conn, pl)
        print("Send data with value: "..value)
        print("Result:\n"..pl)
        conn:close()
        conn = nil
        wifi.sta.disconnect()
        
        node.dsleep(dsleepTime)
    end)
    conn:connect(serverPort, serverIp)
    conn:send("GET /gather/?key=123456&value="..value
        .." HTTP/1.1\r\nHost: " + serverIp + "\r\n"
        .."Connection: keep-alive\r\nAccept: */*\r\n\r\n")

    --connect time out
    tmr.alarm(1,5000,1,function()
        print("[error]connet to server time out")
        --conn:close()
        --conn = nil
        --wifi.sta.disconnect()
        tmr.stop(1)
        
        node.dsleep(dsleepTime)
    end)
end

--读取土壤湿度数据
--一次有效的采样,最少取3次数据,去掉最大值和最小值,再计算平均值
function readValue(adcPin, readTimes)
    if readTimes <= 2 then
        readTimes = 3
    end
    
    local curValue = 0
    local maxValue = 0
    local minValue = 0
    local sumValue = 0
    
    for i = 0, readTimes - 1 do
        curValue = adc.read(adcPin)
        sumValue = sumValue + curValue
        if maxValue < curValue then
            maxValue = curValue
        end
        if (minValue == 0) or (minValue > curValue) then
            minValue = curValue
        end
    end
    sumValue = sumValue - maxValue - minValue
    curValue = math.floor(sumValue / (readTimes - 2))

    return curValue
end

服务器上的HTTP服务,文件site.ini

[uwsgi]
socket = 127.0.0.1:8080
processes = 2
threads = 1
plugins = python3
master = true
pythonpath = /opt/work/web_gather
chdir = /opt/work/web_gather
module = site
callable = app
chmod-socket = 777
memory-report = true

服务器上的HTTP服务,文件site.py

#!/bin/python3

from datetime import datetime
from flask import Flask, request, abort, render_template
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy import desc

app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://root:123456@127.0.0.1/web_gather'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

app.config['HUMIDITY_MIN'] = 740

db = SQLAlchemy(app)

class Humidity(db.Model):
    __tablename__ = 'humidity'
    id = db.Column('id', db.Integer, primary_key=True)
    createDate = db.Column('create_date', db.DateTime)
    sourceValue = db.Column('source_value', db.Integer)
    value = db.Column('value', db.Integer)

    def __repr__(self):
        return '<Humidity % r>' % self.id

@app.route('/', methods=['GET', 'POST'])
def index():
    key = request.args.get('key', '')
    
    if key == app.config['SECRET_KEY']:
        sourceValue = request.args.get('value', '0')
        value = (1024 - int(sourceValue)) / 1024 * 100
        curTime = datetime.today()
        humidity = Humidity(createDate=curTime, sourceValue=sourceValue, value=value)
        db.session.add(humidity)
        db.session.commit()
        return 'true'
    
    humidities = Humidity.query.order_by(desc(Humidity.createDate)).limit(500)
    return render_template('humidity.html', humidities=humidities)

if __name__ == '__main__':
    app.run(debug=False, host='127.0.0.1', port=8080)
    #app.debug = False
    #app.run()

MySQL的表定义

CREATE TABLE `humidity` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `create_date` datetime DEFAULT NULL,
  `source_value` int(11) DEFAULT NULL,
  `value` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2554 DEFAULT CHARSET=utf8

上星期终于搞掂了办公室的CentOS备份。一开始,我们把硬盘A完全复制到硬盘B(用dd命令拷贝整个硬盘),产生了很多误会,也搞出不少问题,差点以为把硬盘A的数据搞丢了。

后来才发现,CentOS挂载硬盘分区时,根据分区的UUID来挂载的。由于两个硬盘的数据完全一致(包括各分区的UUID也一致),所以即使以硬盘A启动电脑,也可能挂载了硬盘B的分区。最后将错就错,按如下步骤解决问题:

1)修改硬盘B中所有分区的UUID
参考以下文章:
linux下硬盘uuid查看及修改设置
http://blog.csdn.net/rainday0310/article/details/6343038

Ext4的分区可以按照该文章,修改分区UUID:

uuidgen | xargs tune2fs /dev/sdb1 -U

但是swap分区不行,只能格式化一下,让其重新生成UUID:

#操作之前,先取消挂载
swapoff /dev/sdb2
#格式化swap分区
mkswap /dev/sdb2

修改后,可以下命令查看各分区的UUID:

blkid /dev/sdb1

2)修改硬盘B上的相关配置文件
此步骤是为了让硬盘B也能直接启动。就是说硬盘A出了什么故障,直接用硬盘B就可以启动服务器了。
a)修改分区挂载文件/etc/fstab,把对应分区的UUID改为新的。
b)修改grub配置文件/boot/grub/grub.conf,把根目录的分区UUID改为最新的。

3)同步两个硬盘的文件
这里采用rsync命令,把硬盘A的数据自动同步到硬盘B上。
a)把硬盘B的根目录挂载到/media/sdb3
b)创建文件/media/rsync_exclude文件,把要排除的文件夹或文件录进去,一行一个,参考内容如下:

/boot/*
/dev/*
/media/*
/lost+found
/proc/*
/sys/*
/tmp/*
/etc/fstab

c)执行以下命令,立即同步文件(以root用户执行):

rsync -avzP --delete --exclude-form=/media/rsync_exclude / /media/sdb3 >> /media/rsync.log

参考:
rsync官方man文档
https://download.samba.org/pub/rsync/rsync.html

CentOS 6.5下rsync服务器安装配置,rsync 客户端
https://segmentfault.com/a/1190000002502991#articleHeader5

4)优化
后面考虑把硬盘B做成USB移动硬盘,这样可以在需要时才接上电源。再考虑接上硬盘B后自动执行同步命令。