认知螺旋

在认知事物的过程中,一个人的外在表现通常是循环往复的。最常见的情况是,起初对某个领域知之甚少,感觉到自己的无知;随着不断学习,又会在某一阶段认为自己已经掌握了大部分知识;而更进一步的深入,又会觉得在许多地方理解不到位,无知感又随之而来。这种现象,可以用螺旋式台阶来比喻,正如下图的上面部分。

螺旋上升

这会产生一个有趣的问题,虽然认识在逐步加深,也就好比沿着台阶不断上升,但将这些台阶投影到一个平面(如图下半部分)会发现不同高度的台阶会投影到同一个区域里,而这意味着什么呢?从内在看,这意味着某些观念或感觉的往复,正如开头的例子里无知感会反复出现;而从外在来看,则是人对外的表现形式有一定的类似。理解了这一点有两方面的好处:首先,在认知事物的过程中,不要因为出现往复感而焦虑,因为这是深入的必经之路,如果没有这种感觉反倒是有问题的;另外,更重要的是我们能够更加理性地观察和分析他人所处的状态。

如果不理解这种螺旋上升和投影的关系,当我们观察到某人的外在表现时,很容易把他们定位在错误的认知程度上。比如在学术上,大师往往能把非常复杂的问题用浅显的语言描述清楚,而一些只知道浅显原理的人也能说出类似的段子,如果不能理性观察,就容易把“浮于表面”错误看成“深入浅出”。再比如在当前的互联网公司里存在许多杂家,他们表面上什么都懂,却无一精通,其理解事物的深度有限,从而对整体的把握也是畸形的,这跟许多从业多年的人比起来,乍一眼看上去是类似的,需要多了解一点才能真正分辨出来。

看《国史大纲》里对战国时期的一系列的描述,包括废井田开阡陌、军人和商人替代贵族等等,总感觉能看到欧洲文艺复兴时期的味道,两者都是封建社会崩溃的过程。但是,为何欧洲随后横扫全球而当年“中国”只是实现了秦朝的大一统?两者类似螺旋上升的不同层次。从战国到文艺复兴,两者相隔千年,生产力在逐渐进步,因此放在战国时期,各国只能对内扩张对外兼并;放在欧洲,则各国有更强的实力去对外扩展和兼并欧洲以外更广大的世界。

2017-04-15更新

近年来中国的互联网行业大都沿袭野蛮生长的模式,其中以华为、阿里为成功的典型。虽然大家习惯认为BAT是一个梯队,但BT和A并非同一时代的企业,与那俩相比A要晚的多。野蛮生长的最大特点是少量精英+大量的廉价劳动力(即“铁打的营盘流水的兵”),最大的特点在工资低、无偿加班、强调狼性文化和价值观。原本奉行精英文化的公司(比如外企、百度),掉头学习这种野蛮模式以自救。

从时间轴上看,这只是生产力发展到一定阶段,为了迎合这种生产力而诞生的一种生产关系。随着各种技术的成熟,不再需要精英阶层从事一线生产活动,这些精英转而成为领导层去带领一些廉价劳动力完成过去看似无法完成的生产活动。然而,社会仍旧向前发展,将会进入下一阶段,而这个阶段又与上上个阶段表现出的现象类似。

简而言之,社会发展的方向是不断加深协作的深度,这将无法依赖现在的野蛮生长模式完成进化。在短期的将来,更合理的模式将是中等规模的精英间的协作。在人力资源受限的情况下,必然依赖全球的人才聚集,突破这种限制,一些高质量的开源项目正是在这种背景下完成的。适应这种状态的公司的模式不再大,依赖少量的精英人才分布式自由组合。为什么是少量呢?因为生产力的发展,已经不再需要那么多人了;为什么是分布式自由组合?因为这样的组织效率是最高的。

截止今天,仍然能看到市面上各种沿袭野蛮生长模式的公司,其中很多已经死掉了,或者在苟延残喘依赖风投的钱续命。另一些看似成功的公司,比如滴滴、膜拜、ofo等等等等,它们的命运让人拭目以待。那些近年来靠野蛮生长走向巅峰的公司,它们必将进入一轮刮骨疗毒般的清理门户。在将来的几年里,这些都值得期待~

《国史大纲》读书笔记

一、当信任何一国之国民,尤其是自称知识在水平线以上之国民,对其本国已往历史,应该略有所知。 (否则最多只算一有知识的人,不能算一有知识的国民。) 二、所谓对其本国已往历史略有所知者,尤必附随一种对其本国已往历史之温情与敬意。 (否则只算知道了一些外国史,不得云对本国史有知识。) 三、所谓对其本国已往历史有一种温情与敬意者,至少不会对其本国以往历史抱一种偏激的虚无主义, (即视本国已往历史为无一点有价值,亦无一处足以使彼满意。) 亦至少不会感到现在我们是站在已往历史最高之顶点, (此乃一种浅薄狂妄的进化观。) 而将我们当身种种罪恶与弱点,一切诿卸于古人。 (此乃一种似是而非之文化自谴。) 四、当信每一国家必待其国民具备上列诸条件者比数渐多,其国家乃再有向前发展之希望。 (否则其所改进,等于一个被征服国或次殖民地之改进,对其国家自身不发生关系。换言之,此种改进,无异是一种变相的文化征服,乃其文化自身之萎缩与消灭,并非其文化自身之转变与发皇。)

Continue Reading →

PHP项目的Git pre-commit hook

将此脚本放在.git/hook/pre-commit就行。实际上git支持多种hooks,根据需要可以在各个阶段自动完成一些任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#!/bin/bash
# Author: Remigijus Jarmalavičius <remigijus@jarmalavicius.lt>
# Author: Vytautas Povilaitis <php-checker@vytux.lt>
#
# XDebug check added by William Clemens <http://github.com/wesclemens>

ROOT_DIR="$(pwd)/"
LIST=$(git diff-index --cached --name-only --diff-filter=ACMR HEAD)
ERRORS_BUFFER=""
for file in $LIST
do
EXTENSION=$(echo "$file" | grep ".php$")
if [ "$EXTENSION" != "" ]; then
ERRORS=$(php -l $ROOT_DIR$file 2>&1 | grep "Parse error")
if [ "$ERRORS" != "" ]; then
if [ "$ERRORS_BUFFER" != "" ]; then
ERRORS_BUFFER="$ERRORS_BUFFER\n$ERRORS"
else
ERRORS_BUFFER="$ERRORS"
fi
echo "Syntax errors found in file: $file "
fi

# Check for xdebug statments
ERRORS=$(grep -nH xdebug_ $ROOT_DIR$file | \
sed -e 's/^/Found XDebug Statment : /')
if [ "$ERRORS" != "" ]; then
if [ "$ERRORS_BUFFER" != "" ]; then
ERRORS_BUFFER="$ERRORS_BUFFER\n$ERRORS"
else
ERRORS_BUFFER="$ERRORS"
fi
fi
fi
done
if [ "$ERRORS_BUFFER" != "" ]; then
echo
echo "Found PHP parse errors: "
echo -e $ERRORS_BUFFER
echo
echo "PHP parse errors found. Fix errors and commit again."
exit 1
else
echo "No PHP parse errors found. Committed successfully."
fi

有用的Git相关汇总

平时收集的一些有用的git命令(持续更新):

  • git reset –hard: 去掉所有改动,比如某些文件被改动了,用这招可以将它们都干掉,主要用于处理刚刚接手的杂乱版本,后面跟上commit号就会跳到对应的版本。
  • git diff –diff-filter=D –name-only -z ¦ xargs -0 git rm 批量删除所有被delete的文件
  • git branch –merged master ¦ grep -v master ¦ xargs git branch -d 删除所有已经与master合并的分支,用于清理中间的一个本地git库
  • git clean -fd: 干掉所有untracked file and folder,上面这招不会去掉新添加的文件或文件夹,靠这招更加彻底清除。
  • git branch -D: 干掉一个分支,主要用于去掉那些已经没有意义的分支,如fix某个bug的分支。
  • git checkout – :去掉被add了的文件,用.表示去掉所有被add的文件。
  • git checkout -b : 从当前分支分出一个新分支,主要用于创建一个feature或bug分支。
  • git diff <commit id1/reflog id> <commit id2/reflog id> : 查看某个文件两个不同版本间的差别。
  • git log :查看某个文件的历史记录,如果没则表示整个branch的历史记录,加–color表示生成一个树状图,-p表示查看修改详情。
  • git fetch会同步git库和本地库的版本信息,而不会真正下载代码。
  • git checkout version_number /path/to/file 将某个文件切换到指定的版本。
  • git stash用于将当前修改缓存下来,方便切到其它分支工作;完成工作后切回来用git stash pop。
  • git 回滚远程代码库

步骤一:本地先回滚到正确的版本。用git reset –hard version_number version_number可以是commit号或reflog编号。

步骤二:将本地版本强制推送到远端,覆盖远程版本。用git push origin master -f

步骤三:将线上服务器同步到git代码库。直接用git pull origin master是不行的,因为这时候往往线上服务器比代码库更新。所以需要执行下面的命令来强制同步:

  1. git fetch –all
  2. git reset –hard origin/master

    这样就同步好了。基本原理可以参考http://stackoverflow.com/questions/1125968/force-git-to-overwrite-local-files-on-pull。这样做比直接删代码风险要小一些。
  • git log -p –name-only 其中的–name-only是个非常好用的参数,可以用来只显示改动的文件,可以与其它很多命令搭配起来用。
  • git init –bare用于在server端新建一个repo,随后在本地代码文件夹下git init再git add remote对应的地址就可以将本地与server关联上了。
  • git checkout -b –orphan branchname 从当前分支产生一个无历史的新分支,可以用来分享代码。
  • git config –global push.default current 设置全局push默认的分支为当前分支,执行后可以通过git push来推送当前分支,而不需要显式写出分知名。
  • git reflog 查看本地的git命令执行历史。
  • git fetch 同步本地和远程的分支信息,尤其可以消除“Your branch is ahead of ‘origin/master’ by 1 commit.”这样的提醒。
  • git mv old_name new_name 用于重命名一个文件。免去rm+add的重复劳动。
  • git branch -a 查看远程分支。
  • git push origin –delete branch_name 根据分支名删除远程分支。
  • git push origin –delete tag tag_name 根据tag删除远程分支。
  • git checkout mybranch; git rebase origin 把自己的分支以patch的形式加到origin分支上,与pull或merge产生的效果类似,但“看起来“历史不同,参考http://gitbook.liuhui998.com/4_2.html。

MySQL安装部署

参考http://dev.mysql.com/doc/refman/5.6/en/installing-source-distribution.html。

  • 安装前配置
    groupadd mysql
    useradd -r -g mysql mysql
    yum install cmake
    
  • 安装

    tar zxvf mysql-VERSION.tar.gz
    cd mysql-VERSION
    cmake .
    make
    make install
  • 安装后配置
cd /usr/local/mysql
chown -R mysql .
chgrp -R mysql .
# df -ha 用于查看各个分区的使用率,在处理之前确保有足够存储空间
bin/mysql_install_db --datadir=/var/lib/mysql --user=mysql
chown -R root .
chown -R mysql data
bin/mysqld_safe --user=mysql &
mysqladmin -u root -h host_name password newpassword
# 启动服务
/usr/local/mysql/bin/mysqld_safe --user=mysql &;
# my.cnf在/etc目录下
  • 设置用户

    #创建一个新用户,host可以用%替换,表示从任意ip地址登陆
    CREATE USER ‘username‘@’host’ IDENTIFIED BY ‘password’;

#查看用户信息
SELECT user, host, password FROM user WHERE user=’username’;

#分配权限,primitive包括select, insert, update, delete, create, drop, index, alter, grant, reference, reload, shutdown, process, file共14个权限
GRANT primitive1, primitive2, …, primitiven ON db.table TO username@host IDENTIFIED BY password

#删除用户
DROP USER username;

#取消用户授权
REVOKE primitive ON db.table FROM username@host;

  • 备份
    下面的命令里都省略了-u和-p,在实际操作时须补全。
    关于bin log的部分,文档《binlog基本认识》有非常详尽的介绍。

    开启binlog

    编辑my.cnf文件,在[mysqld]区块中加入log-bin=mysql-bin,重启服务即生效
    执行show variables like ‘log_%’;来确认已经打开bin log

常用的bin log操作

show master logs; # 查看所有bin log日志列表
show master status; # 查看最新一个bin log日志编号名称,以及最后一个操作事件结束点
flush logs; # 刷新日志,产生一个新的bin log日志文件
reset master; # 清空所有bin log日志

查看bin log

mysqlbinlog mysql-bin.000013 #打开一个bin log文件
show binlog events [IN log_name] [FROM pos] [LIMIT [offset,] row_count]
例如指定查询 mysql-bin.000021 这个文件,从pos点:8224开始查起,偏移2行,查询10条
show binlog events in ‘mysql-bin.000021’ from 8224 limit 2,10\G;

利用bin log恢复数据

mysqlbinlog mysql-bin.0000xx | mysql

备份整个数据库,备份前需要获取全局读锁,因此必须在traffic较低的时候进行

mysqldump –single-transaction –all-databases > backup_yy_mm_dd_hh_mm_ss.sql
mysql < backup_yy_mm_dd_hh_mm_ss.sql

    ## 设置主服务器
    # 确保master已经通过log-bin来开启bin log
    # 确保每个host对应的server-id是不同的
        [mysqld]
        log-bin=mysql-bin
        server-id=1
        innodb_flush_log_at_trx_commit=1
        sync_binlog=1
    # 为slave创建专门的用户名
    create user 'repl'@'%.mydomain.com' identified by 'slavepass';
    grant replication slave on *.* to 'repl'@'%.mydomain.com';

    ## 设置从服务器
    # 确保slave的server-id与其它服务器不同
        [mysqld]
        server-id=2

    ## 获取master的同步位置
    # 执行flush操作,开启一个client session,执行下面语句后保持不退出
    flush tables with read lock;
    # 在另一个client session执行,记录File、Position对应的值
    show master status;
    +------------------+----------+--------------+------------------+
    | File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
    +------------------+----------+--------------+------------------+
    | mysql-bin.000003 | 73       | test         | manual,mysql     |
    +------------------+----------+--------------+------------------+
    # 回到锁住的session执行
    unlock tables;

    ## 创建master的镜像
    mysqldump --all-database --master-data > dbdump.db
    # 如果需要选择性同步,需要在slave上配置--replicate-ignore-db=db_name来完成,详细使用方法参考官方文档

    ## 在slave导入镜像
    #带--skip-slave-start选项启动slave上的mysql
    #导入dump文件
    mysql < dbdump.db
    #在slave执行change master to指令
    CHANGE MASTER TO
         MASTER_HOST='master_host_name',
         MASTER_USER='replication_user_name',
         MASTER_PASSWORD='replication_password',
         MASTER_LOG_FILE='recorded_log_file_name',
         MASTER_LOG_POS=recorded_log_position;
    start slave;

Nginx+PHP安装部署

这里记录安装部署Nginx+PHP相关的一些东西。

环境+依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 基本编译环境
yum -y install gcc automake autoconf libtool make gcc-c++ glibc
# PHP依赖
yum -y install libmcrypt-devel mhash-devel libxslt-devel libjpeg \
libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 \
libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel \
bzip2 bzip2-devel ncurses ncurses-devel curl curl-devel e2fsprogs \
e2fsprogs-devel krb5 krb5-devel libidn libidn-devel openssl openssl-devel
# 安装pcre:
cd /usr/local/src
wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-xx.xx.tar.gz
tar -zxvf pcre-xx.xx.tar.gz
cd pcre-xx.xx
./configure
make
make install
# 安装zlib:
cd /usr/local/src
wget http://zlib.net/zlib-x.x.x.tar.gz
tar -zxvf zlib-x.x.x.tar.gz
cd zlib-x.x.x
./configure
make
make install
# 安装ssl:
cd /usr/local/src
wget http://www.openssl.org/source/openssl-x.x.x.tar.gz
tar -zxvf openssl-x.x.x.tar.gz

安装PHP

安装

这里可能遇到各种缺少依赖的错误,遇到什么就安装什么就行,可以参考文章“20+ common PHP compilation errors and fix”。这里的配置项比较多,也很难对每一项都非常了解,所以其实可以设置少一些选项,然后等真的需要的时候再重新编译。这其实非常容易,重新安装一遍后重启php-fpm就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
wget http://cn2.php.net/distributions/php-xx.xx.xx.tar.gz
tar zvxf php-xx.xx.xx.tar.gz
cd php-xx.xx.xx.tar.gz
./configure
--prefix=/usr/local/php \
--enable-fpm \
--with-fpm-user=www-data \
--with-fpm-group=www-data \
--with-mcrypt \
--enable-mbstring \
--disable-pdo \
--with-curl \
--disable-debug \
--disable-rpath \
--enable-inline-optimization \
--with-bz2 \
--with-zlib \
--enable-sockets \
--enable-sysvsem \
--enable-sysvshm \
--enable-sysvmsg \
--enable-pcntl \
--enable-mbregex \
--with-mhash \
--enable-zip \
--with-pcre-regex \
--with-mysql \
--with-mysqli \
--with-gd \
--with-jpeg-dir \
--with-openssl \
--enable-pdo \
--with-pdo-mysql \
--with-mysql-sock=/var/lib/mysql/mysql.sock \
--with-config-file-path=/usr/local/php/etc

make all install
# 把php.ini放到指定的位置
cp php.ini-development(根据需要换成其它) /usr/local/php/etc/php.ini
# 配置php.ini中的timezone参数为Asia/Shanghai

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cd /usr/local/php
cp etc/php-fpm.conf.defautl etc/php-fpm.conf
vi etc/php-fpm.conf
# 修改
user = www-data
group = www-data
# 如果www-data用户不存在则添加www-data用户:
groupadd www-data && useradd -g www-data www-data
# 添加php到环境变量
vi /etc/profile
# 在底部增加一行export PATH=/usr/local/php/bin:$PATH
source /etc/profile
# 检查php cli配置文件
php --ini 若输出路径与--with-config-file-path对应的路径不同,需要建一个软链

启动/重启

1
2
3
#启动:/usr/local/php/sbin/php-fpm
#关闭:kill -INT pid
#重启:kill -USR2 pid

php.ini的问题

偶然发现一个问题,phpinfo()里输出的配置文件路径与php –ini的路径不同。而实际上在编译PHP的时候明确设定了–with-config-file-path参数,设置与phpinfo()输出相同,与php –ini不同。这与文档中描述的有点不一致。

搜索了一把,发现phpinfo()实际对应php/bin/php-cgi,而运行php –ini的时候对应的是php/bin/php,因而两者调用的实际上是不同的可执行文件。只能理解,在我使用的版本里,cli对应的php.ini与cgi的不同。解决方法最简单的则是在php –ini里显示的配置文件路径上放一个php.ini软链,这就好了。

Composer的应用

Composer对于框架的各个组件来说,就好像Container对于各种类一样。如果用一句话来概括Composer的作用,那应该是:

Composer is a tool for dependency management in PHP.

从实际应用的角度,Composer的作用体现在两个方面:

  1. 安装和维护项目依赖的第三方库
  2. 支持项目内部autoload

用Composer安装第三方库

在Composer的支持下,安装和使用第三方库非常方便。大概步骤包括(1)更改composer.json;(2)执行composer install下载和安装库;(3)在项目的入口中引用vendor目录下的autoload.php。一个最简单的composer.json看起来是这样的:

{
“require”: {
“monolog/monolog”: “1.0.*”
}
}

它定义了项目依赖的第三方库。

用Composer支持autoload

虽然autoload.php位于vendor目录下,但它实际上也支持项目自身的autoload。Composer支持4种autoload方式,分别是psr-0、psr-4、classmap和files。其中psr-0/psr-4属于按规则autoload,而classmap和files则属于按配置autoload。所谓“按规则”是指:只要文件命名、文件路径、类命名符合一定规则,autoloader就能根据类的命名空间和类名加载对应的文件。先举个psr-4的例子:


$obj = new App\MyLib\TestComposer();

假设composer.json像下面这样:


{
“autoload”: {
“psr-4”: {
“App\“: “src/“,
}
}
}

那么对应的文件将是src/MyLib/TestComposer.php里的TestComposer类。如果上面的规则是psr-0,那么对应的文件则是src/App/MyLib/TestComposer.php。可以看到两者的差别其实很小,psr-4里是把左边(App\)替换成右边(src/)。psr-0则是把左边连接到右边后面。因此,psr-4的一个主要目的是简化项目的目录结构。

另外两种方式classmap和files比较容易理解,如下面的例子:


{
“autoload”: {
“classmap”: [“src/MyLib/“, “TestComposer.php”],
“files”: [“src/MyLib/functions.php”]
}
}

classmap针对文件里定义的class,可以配置整个目录或某个文件。files则用于加载非class的内容,例如函数实现。

最后,增加了新的文件或修改了composer.json还需要执行


composer dump-autoload

来重新生成autoload逻辑。对于psr-0/psr-4的还可以加-o来优化autoload速度。

安装Nginx

安装

1
2
3
4
5
6
7
8
9
10
11
cd /usr/local/src
wget http://nginx.org/download/nginx-x.x.x.tar.gz
tar -zxvf nginx-x.x.x.tar.gz
cd nginx-x.x.x
./configure --sbin-path=/usr/local/nginx/nginx \
--conf-path=/usr/local/nginx/nginx.conf \
--pid-path=/usr/local/nginx/nginx.pid --with-http_ssl_module \
--with-pcre=/usr/local/src/pcre-xx.xx --with-zlib=/usr/local/src/zlib-x.x.x \
--with-openssl=/usr/local/src/openssl-x.x.x
make
make install

启动

1
sudo /usr/local/nginx/nginx

配置实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
user  www-data;
worker_processes 1;
pid logs/nginx.pid;

events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$request_time"';
#[important] need to specify the index
index index.php index.html index.htm;

access_log logs/access.log main;
error_log logs/error.log;

sendfile on;
tcp_nopush on;
keepalive_timeout 65;

#若有多个server,建议分为多个文件,每个包含一个server模块,用include包含
server {
listen 80;
server_name example.com;

#access_log logs/host.access.log main;

#[important] need to make sure each subpath in the root have +x permission
root /home/work/laravel/public;

#[important] need to do the rewrite here
location / {
try_files $uri $uri/ /index.php?$query_string;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#[important] need to have the * below
location ~* \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
location ~ /\.ht {
deny all;
}
}
}

维护

1
2
3
4
5
# 把下面的命令放在脚本里用于备份配置文件
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.$(date "+%b_%d_%Y_%H.%M.%S")
#重新加载conf:
#测试:sudo /usr/local/nginx/nginx -t
#重载:sudo /usr/local/nginx/nginx -s reload

基于惯性传感器的姿态恢复

一个物体在空间中的存在可以从两个维度描述:一方面是在什么位置,这可以由其所在坐标$${x,y,z}$$来描述;另一方面是处于什么姿态(orientation),例如它是倾斜的还是正的。要知道一个物体在空间的位置是困难的,需要借助外在测量系统,例如GPS利用24颗卫星定位一个物体在地球上的经纬度和海拔。即便有GPS,要知道物体在室内的位置还是不行,需要相应的室内定位技术。扯远了,室内定位以后再说。要知道一个物体的姿态,相对容易一些,这可以借助惯性传感器(inertial sensor)。

惯性传感器通常指代下面三个具体的传感器:

  • 加速度计(accelerometer):测量三轴加速度,单位$$m/s^2$$;
  • 陀螺仪 (gyroscope):测量绕三轴旋转的角速度,单位是$$degree/s$$;
  • 磁力计(magnetometer):测量三轴磁力的大小,单位是$$tesla$$;

惯性传感器的应用非常广泛,几乎遍及所有的智能硬件设备(如手机、手环等)。它能用于姿势识别,如apple watch自动识别用户是否正在看表;zepp捕捉网球、棒球姿势辅助训练;手环识别走路、跑步、骑车等等。这些看似神奇的应用都基于惯性传感器,以后有机会再深入讨论这些算法。

怎么才能测量一个物体的姿态呢?首先我们必须假定该物体与惯性传感器之间是刚体连接的,这样通过读取惯性传感器的值就能推测物体的姿态。加速度计可以用来测量物体的倾斜。如果物体处于静止状态,因为我们知道重力的方向总是向下的,这是非常容易做到的。如果物体静止,测量到的加速度方向就是重力的方向。此外,陀螺仪也可以用来计算物体的姿态。假定已知物体的初始姿态,那么我们将物体沿三轴旋转的角速度积分,就知道物体的姿态了。

IMU sensor

但实际情况比这复杂。物体可能处于运动状态,而陀螺仪读数有严重的漂移,使得姿态估计并不容易。现有的主流思路是结合加速度计和陀螺仪,根据两者的读数分别估算物体姿态,然后依据某种方法将两方面融合起来。其中的算法细节,没有比这篇文章讲得更清楚的了,感兴趣的话可以好好读一遍(A practical approach to Kalman filter and how to implement it),中文翻译)。

Demo采用ArduinoSparkFun 9Dof Sensor Stick。连接和代码借鉴这篇文章,讲得比较详细,照着做一遍就行了,代码参考这里。在上面代码的accelerometer_norm和gyro_comp需要用到两个数组,需要在校准中获得。

arduino+spark

传感器得到的原始读数无法直接使用。例如加速度计在水平静止时的读数可能是$$ x=0, y=0, z=-211$$,这里的211其实是g的大小,而我们需要把这个值与g对应起来。对应上面的代码,accelerometer_norm[2]就应该是211。同理,需要找到x轴、y轴与g的对应关系。将传感器的x轴、y轴与水平面垂直静止放置(即分别使x轴、y轴朝上),得到的读数分别对应accelerometer_norm[0]和accelerometer_norm[1]。陀螺仪的校准与加速度计不同。陀螺仪的问题在于即使处于静止状态,各个轴角速度一般都不为0,这是明显错误的。所以简单的做法是把传感器静止放置,记录陀螺仪各个轴角速度测量值,例如$$ x=5, y=30, z=-10$$,那么对应的gyro_comp[3] = {-5, -30, 10}。需要注意的是,这里的校准是非常粗糙的,也并不能完全消除误差,而实际的误差模型非常复杂,如果需要得到更加精确的测量结果还需要更加复杂的校准过程。

姿态恢复采用开源项目。这个项目本来是针对xio自产的传感器设备,为了接入自己的设备需要做一些修改。只需要修改program.cs

建议安装Arduino IDE则不需要安装额外的驱动了。

改变传感器的姿态可以从Visual Studio执行的项目中看到下面的画面:

IMU sensor

RabbitMQ报错[ERROR: node with name 'rabbit' already running on 'localhost']

这个报错是在执行./rabbitmq-server -detached的时候报出来的,除了这句以外没有任何信息。这个报错的本意是rabbit@localhost已经在运行了,不允许再启动一个实例。于是尝试干掉已有的进程:

ps aux | grep rabbit

执行后发现没有任何进程。尝试Google类似的问题,找了很久终于发现一个可能的问题——/etc/hosts文件可能被改出问题了。

这要回到RabbitMQ的一个基本认识上,它是通过hostname来唯一确定本机的,甚至包括队列存储都与hostname直接对应,如果hostname被改了,意味着实例本身都变化了。于是执行

hostname -s

发现居然没问题,还是localhost。所以,的确感觉非常奇怪。

进一步检查/etc/hosts文件,发现无意间把localhost指向了局域网内的另一台机器:

192.168.1.56 localhost

于是终于找到原因了,RabbitMQ启动时检查localhost,发现上面已经启动了一个RabbitMQ实例,于是报了最前面的错误。只需要去掉hosts文件里的配置就好了。

上面说的是主要原因,另一个意外操作导致这个错误的原因是erlang的守护进程被其它用户启动了,这可以通过:

ps aux | grep epmd

检查。需要干掉这个进程才能顺利把RabbitMQ启动起来。

Dependency Injection

面向对象的一条准则就是将类与类之间的依赖关系解耦,这并不是什么新概念。Dependency Injection(DI)的目的也是一样,引用《implementing laravel》的一句原文:

Dependency Injection is the act of adding (injecting) any dependencies into a class, rather than instantiating them somewhere within the class code itself.

举一个例子来区分这两种情况:

class NoDI 
{
    function __construct()
    {
        $this->inner_class = new InnerClass();
    }
}

class DI
{
function __construct(SomeInterface $implementation)
{
$this->inner_class = $implementation;
}
}

上面前一种情况下,如果inner_class的实现发生变化,例如需要重写一个NewInnerClass(),那么需要在NoDI中修改这行代码。而后一种情况下,由于inner_class是从外部传入的,所以DI类本身不需要发生任何变化。

这个道理并不新颖,所以乍一眼看来这没有什么有意思的东西。如果无论什么情况都需要为类额外写一个interface,反倒是一种负担。另外,如果类之间的依赖关系比较复杂,使用起来也不方便,例如:类A依赖类B,类B又依赖类C,那么如果要获取一个A的对象,需要先得到B的对象,进一步递推到C的对象。

为了减少这种重复的实例化过程,Laravel提供了Container。实际上一个Laravel的APP对象本身就继承自Container。还是以A、B、C之间的依赖关系为例:


interface B_Interface {}
interface C_Interface {}

class A {
function __construct(B_Interface $b){}
}

class B implements B_Interface {
function __construct(C_Interface $c){}
}

class C implements C_Interface {
function __construct(){}
}

在没有Container的情况下,会像下面一样来实例化A对象:


$c = new C();
$b = new B($c);
$a = new A($b);

试想我们每次获取A对象都需要写这样的3行代码。在面向对象的开发中,类之间的依赖关系是非常复杂的,这使得这样实例化变得不可行。这或许是许多人更加倾向于面向过程来开发(即使他们的代码里也充满了类定义)。

有了Container以后这样的情况得到了很大的改善。如下面所示:


APP::bind(‘B_Interface’, ‘B’);

APP::bind(‘C_Interface’, function(){
return new C();
});

$a = APP::make(‘A’);

在Laravel中,上面的代码会这样执行:


  1. 尝试实例化类A,发现其依赖B_Interface

  2. 尝试找到实现B_Interface的类,发现已注册的B_Interface指向B类

  3. 尝试实例化类B,发现其依赖C_Interface

  4. 尝试找到实现C_Interface的类,发现已注册的C_Interface执行一个closure,返回一个C类对象

  5. 按照上面相反的顺序依次获得C、B、A对象


Laravel自动完成上面这样的递归式实例化过程。在PHP中,只要利用反射就能做到这一点。也许有人会说,上面这样的代码岂止3行,比前面的实现更加麻烦了。但是,在真实的场景下,两个bind语句是由服务提供方来实现的,而且只要做一次,在全局都能使用。所以,其实只需要最后一句话就够了。

上面的例子里,生成实现B_Interface的类的时候用的是:


APP::bind(‘B_Interface’, ‘B’);

但对于C_Interface,用的是closure。这样做是由于B依赖于C_Interface,如果也用closure的话就不方便写了。

Container在Laravel处于绝对核心地位。可以说所有类实例化都是经过Container来完成的,上面举的例子其实只是一种用法而已,在使用Laravel的过程中,由于Container的存在还有非常多技巧。