在Android上使用tcpdump

tcpdump工具是分析网络协议和数据包的利器,也可以在Android上使用(需要root)。

首先在android上安装tcpdump

wget http://www.strazzere.com/android/tcpdump
adb push tcpdump /data/local/tmp/tcpdump
adb chmod 755 /data/local/tmp/tcpdump

然后使用root用户启动tcpdump,在android上进行相应的操作后,按ctrl+c中断

adb shell
shell@android:/ $ su
root@android:/ # /data/local/tmp/tcpdump -h                                    
tcpdump version 3.9.8
libpcap version 0.9.8
Usage: tcpdump [-aAdDeflLnNOpqRStuUvxX] [-c count] [ -C file_size ]
		[ -E algo:secret ] [ -F file ] [ -i interface ] [ -M secret ]
		[ -r file ] [ -s snaplen ] [ -T type ] [ -w file ]
		[ -W filecount ] [ -y datalinktype ] [ -Z user ]
		[ expression ]
root@android:/ # /data/local/tmp/tcpdump -p -vv -s 0 w /sdcard/capture.pcap

tcpdump会在/sdcard下生成文件,可以通过adb pull /sdcard/capture.pcap把文件传到PC上用wireshark看,也可以直接在android上通过SharkReader看。

[Read More]

使用Ping来检查网络连通性

树莓派使用了一个无线网卡连接家里的无线路由器,在实际使用过程中发现连续运行多天后会掉线,而且掉线后基本上就再也连不上网了,需要重启树莓派才能恢复,十分麻烦。

假设无线路由器IP是192.168.1.1,于是每隔15分钟检查一下,是否能从树莓派上ping通路由器;如果不能则重启无线网络,脚本如下:

network.sh

#!/bin/bash

export PATH=/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin

ping_count() {
  count=0
  `timeout 5 ping 192.168.1.1 | while read LINE; do
  {
        if [[ "${LINE}" =~ "64 bytes from" ]]; then
                let "count = $count + 1"
                echo "export count=$count"
        fi
  }
  done`
  echo $count
}


if [[ $(ping_count) < 1 ]]; then
        ifconfig wlan0
        ifconfig wlan0 down
        sleep 1
        ifconfig wlan0 up
        sleep 1
        netcfg -r wlan0-Hugo-Nas
        sleep 5
        if [[ $(ping_count) < 1 ]]; then
                echo "Fatal error: wifi is down, rebooting now..."
                reboot
        fi
fi

可以用ifconfig wlan0 down && ./network.sh来测试脚本是否能正常工作,测试完成后就可以放到crontab里执行了。

[Read More]

树莓派的GPIO接口输出电流限制

树莓派提供了一个连接头让我们访问CPU的17个GPIO接口,如下图

这些接口可配置成输入或输出。本文主要讨论GPIO引脚作为输出时电流的限制。

阻抗 (impendance)

阻抗和和电阻的区别(resistance)在于电阻的阻值是固定的,不会随着电流变化,阻抗则不然,可能随着外部变化,如电流或频率变化。从另一个角度来说,电阻是线性的,但阻抗不是。比如放大器的阻抗会随着输出的信号频率变化。

树莓派的的每个GPIO引脚都有一个寄存器可以设置引脚的驱动强度,也就是在保持输出电压为逻辑0和1的情况下,可以改变阻抗的大小从而改变GPIO引脚的输出电流大小。

通过如下电路测量相同电流下不同阻抗对应的GPIO电压输出(其中用到了一个电位器调节电流保持恒定):

通过计算后,下表是当输出电流为2,4 … 16mA时,对应的阻抗大小以及如果发生短路时的短路电流大小。

可以看出短路电流都是超过16mA的。

一个发光二极管压降约为1.52.0v,工作电流为310v

GPIO引脚的电流是通过板上的3.3V电压调整器输出的,树莓派是按平均每个引脚3mA来设计的,所以总的电流不能超过17 * 3 = 51mA。

结论

树莓派引脚电流大小的限制是:每个引脚最大输出电流为16毫安(mA),且同一时刻所有引脚的总输出电流不超过51毫安

参考链接

  1. http://www.thebox.myzen.co.uk/Raspberry/Understanding_Outputs.html

使用8位移位寄存器74HC595扩展树莓派的IO端口

树莓派的GPIO接口数目有限,驱动一个步进电机需要占用4个, 一个Nokia 5110液晶也要占4个, 传感器输入至少需要一个,多玩几个外设后接口就不够用了。如果接口可以复用就可以让树莓派驱动更多的外设了,本文讨论如何使用74HC595集成电路芯片来扩展树莓派的I/O接口。

芯片介绍

SN74HC595N是德州仪器公司生产的集成电路芯片,是一个8位串行输入变串行输出或并行输出移位寄存器,具有高阻关断,高电平和低电平三态输出。在IO扩充上,可以最多串联15片,也就是高达120个IO扩充。

(注意到芯片上的小凹槽了吗,拿芯片的时候以这个为参考物就不会搞反了)

接口的常用命名方式有以下两种:

接口代号(编号)说明接口代号(编号)说明
Q7’(9)serial data outputQH’ (9)serial data output
MR (10)Master Reset (Active Low)SRCLR (10)Shift register CLeaR
SH_CP (11)shift register clock inputSRCLK (11)Shift Register CLocK input
ST_CP (12)storage register clock inputRCLK (12)storage Register CLocK input
OE (13)output enable input (Active Low)OE (13)Output Enable
DS (14)serial data inputSER (14)SERial data input
Qx (15,1-7)data outputQx (15,1-7)data output

控制流程

如果要在8个引脚输出01010101

[Read More]

树莓派网站容灾:利用DNSPod,Google App Engine和Github

背景介绍

把网站托管在树莓派上后如果家里停电或是宽带故障,会造成网站中断。本文提供一个免费的解决方案(前提是你需要有自己的一个域名,并由DNSPod解析)

DNSPod

首先需要在DNSPod里设置好需要failover的域名CNAME:比如hugozhu.myalert.info

其中默认指向pi.myalert.info, 这是一个域名的A Record,会由运行在树莓派上的脚本来更新动态IP,国外则指向github。当停电时我们需要自动把`默认`这条纪录修改成github。

使用下面命令获得相应CNAME的domain_id:

curl -k https://dnsapi.cn/Domain.List -d "login_email=xxx&login_password=xxx" 

使用下面命令获得相应CNAME的record_id:

curl -k https://dnsapi.cn/Record.List -d "login_email=xxx&login_password=xxx&domain_id=xxx"

Google App Engine

切换DNS脚本

package dnspod

import (
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

const (
	login_email    = "<your_login_email>"
	login_password = "<your_login_password>"
	format         = "json"
	domain_id      = "<domain_id>"
	record_id      = "<record_id>"
	sub_domain     = "<your_subdomain>"
	record_type    = "CNAME"
	record_line    = "默认"
	ttl            = "600"
)

func Update(client *http.Client, cname string) string {
	body := url.Values{
		"login_email":    {login_email},
		"login_password": {login_password},
		"format":         {format},
		"domain_id":      {domain_id},
		"record_id":      {record_id},
		"sub_domain":     {sub_domain},
		"record_type":    {record_type},
		"record_line":    {record_line},
		"value":          {cname},
		"ttl":            {ttl},
	}
	req, err := http.NewRequest("POST", "https://dnsapi.cn/Record.Modify", strings.NewReader(body.Encode()))
	req.Header.Set("Accept", "text/json")
	req.Header.Set("Content-type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return err.Error()
	}
	defer resp.Body.Close()
	bytes, _ := ioutil.ReadAll(resp.Body)
	return string(bytes)
}

检测接口

部署一个web应用到Google App Engine上,该应用接受树莓派上的一个URL(注意这里不应该用需failver的域名),并请求该域名以检查网站是否正常。这里也可以使用监控宝来监控,但只有付费专业版才支持出错后回调URL。

[Read More]

使用Goroutine和Channel实现按键超时交互

背景介绍

前面的文章(见参考链接)已经介绍了如何使用按键作为树莓派的输入。在实际应用中可以通过按下按键循环显示预先设定的脚本输出到显示屏幕,需求如下:

  1. 如果按键不被触动,则定时5秒执行脚本获取最新内容显示;
  2. 因为不同的脚本获取内容速度会不一样,我们要求如果超过500ms脚本还未返回,需要在屏幕上显示“loading…”这样的过渡内容,如果脚本在500ms内返回,则不显示。

使用Goroutine和Channel可以很方便的实现这个需求。

代码

var screen_chan chan int
var switch_chan = make(chan bool)

func main() {
	//a goroutine 检查按键是否被按
	go func() {
		last_time := time.Now().UnixNano() / 1000000
		btn_pushed := 0
		total_mode := 3
		for msg := range WiringPiISR(PIN_GPIO_6, INT_EDGE_FALLING) {
			if msg > -1 {
				n := time.Now().UnixNano() / 1000000
				delta := n - last_time
				if delta > 300 { //如果两次按键变化的间隔时间<300ms,是因为接触信号不稳定可以忽略掉
					last_time = n
					btn_pushed++
					screen_chan <- btn_pushed % total_mode
				}
			}
		}
	}()

	//a goroutine 根据管道消息刷新屏幕
	go loop_update_display()

	//选择确实的屏幕内容脚本编号
	screen_chan <- 0

	//a goroutine: 定时5s向管道发送更新屏幕内容的信号
	ticker := time.NewTicker(5 * time.Second)
	go func() {
		for {
			<-ticker.C
			screen_chan <- -1
		}
	}()
	
	...	
}

func loop_update_display() {
	current_screen := 0
	for msg := range screen_chan {
		switch_screen := false
		if msg >= 0 {
		   //说明是按钮触发的消息,而不是定时器触发的(-1)
			if msg != current_screen {
				//btn pushed
				current_screen = msg
				switch_screen = true
				go func() {
					select {
					case <-time.After(500 * time.Millisecond):
						display_loading()
						<-switch_chan
					case <-switch_chan:
					}
				}()
			}
		}
		switch current_screen {
		case 0:
			display_screen0()
		case 1:
			display_screen1()
		case 2:
			display_screen2()
		}
		if switch_screen {
			switch_chan <- true
		}
	}
}
go func() {
	select {
	case <-time.After(500 * time.Millisecond):
		display_loading(current_screen)
		<-switch_chan
	case <-switch_chan:
	}
}()

超时控制的代码就是上面几行了。首先如果是按键触发,主goroutine会创建一个检查超时的goroutine,该goroutine执行select语句时会试图从两个管道里获取消息,先获取到消息的管道会继续执行相应分支的代码。

[Read More]

Go语言内存模型

名词定义

执行体 - Go里的Goroutine或Java中的Thread

背景介绍

内存模型的目的是为了定义清楚变量的读写在不同执行体里的可见性。理解内存模型在并发编程中非常重要,因为代码的执行顺序和书写的逻辑顺序并不会完全一致,甚至在编译期间编译器也有可能重排代码以最优化CPU执行, 另外还因为有CPU缓存的存在,内存的数据不一定会及时更新,这样对内存中的同一个变量读和写也不一定和期望一样。

Java的内存模型规范类似,Go语言也有一个内存模型,相对JMM来说,Go的内存模型比较简单,Go的并发模型是基于CSP(Communicating Sequential Process)的,不同的Goroutine通过一种叫Channel的数据结构来通信;Java的并发模型则基于多线程和共享内存,有较多的概念(violatie, lock, final, construct, thread, atomic等)和场景,当然java.util.concurrent并发工具包大大简化了Java并发编程。

Go内存模型规范了在什么条件下一个Goroutine对某个变量的修改一定对其它Goroutine可见。

Happens Before

在一个单独的Goroutine里,对变量的读写和代码的书写顺序一致。比如以下的代码:

package main

import (
	"log"
)

var a, b, c int

func main() {
	a = 1
	b = 2
	c = a + 2
	log.Println(a, b, c)
}

尽管在编译期和执行期,编译器和CPU都有可能重排代码,比如,先执行b=2,再执行a=1,但c=a+2是保证在a=1后执行的。这样最后的执行结果一定是1 2 3,不会是1 2 2。但下面的代码则可能会输出0 0 01 2 2, 0 2 3 (b=2比a=1先执行), 1 2 3等各种可能。

package main

import (
	"log"
)

var a, b, c int

func main() {
	go func() {
		a = 1
		b = 2
	}()
	go func() {
		c = a + 2
	}()
	log.Println(a, b, c)
}

Happens-before 定义

Happens-before用来指明Go程序里的内存操作的局部顺序。如果一个内存操作事件e1 happens-before e2,则e2 happens-after e1也成立;如果e1不是happens-before e2,也不是happens-after e2,则e1和e2是并发的。

[Read More]

使用Go语言在树莓派上编程

WiringPi是树莓派上比较好的一个开发库,是用C语言写的。使用cgo,我们可以在Go语言里方便的调用WiringPI的函数,于是我包装了一个WiringPi-Go,目前支持wiringPi的基本功能,硬件SPI协议驱动Nokia 5110屏幕,以及中断,未来还会增加PWM和I2C协议的支持。

下面是一个完整的使用例子,结合了之前的两个电路:链接1链接2

通过push button可以切换液晶屏显示不同脚本的输出内容。

lcd_switch.go

package main

import (
	. "github.com/hugozhu/rpi"
	"github.com/hugozhu/rpi/pcd8544"
	"log"
	"os/exec"
	"time"
)

const (
	DIN        = PIN_MOSI
	SCLK       = PIN_SCLK
	DC         = PIN_GPIO_2
	RST        = PIN_GPIO_0
	CS         = PIN_CE0
	PUSHBUTTON = PIN_GPIO_6
	CONTRAST   = 40 //may need tweak for each Nokia 5110 screen
)

var screen_chan chan int
var TOTAL_MODES = 3

func init() {
	WiringPiSetup()
	pcd8544.LCDInit(SCLK, DIN, DC, CS, RST, CONTRAST)
	screen_chan = make(chan int, 1)
}

func main() {
	//a goroutine to check button push event
	go func() {
		last_time := time.Now().UnixNano() / 1000000
		btn_pushed := 0
		for pin := range WiringPiISR(PUSHBUTTON, INT_EDGE_FALLING) {
			if pin > -1 {
				n := time.Now().UnixNano() / 1000000
				delta := n - last_time
				if delta > 300 { //software debouncing
					log.Println("btn pushed")
					last_time = n
					btn_pushed++
					screen_chan <- btn_pushed % TOTAL_MODES //switch the screen display
				}
			}
		}
	}()

	//a groutine to update display every 5 seconds
	go loop_update_display()

	//set screen 0 to be default display
	screen_chan <- 0

	ticker := time.NewTicker(5 * time.Second)

	for {
		<-ticker.C
		screen_chan <- -1 //refresh current screen every 5 seconds
	}
}

func loop_update_display() {
	current_screen := 0
	for screen := range screen_chan {
		if screen >= 0 {
			if screen != current_screen {
				//btn pushed
				current_screen = screen
				display_loading()
			}
		}
		switch current_screen {
		case 0:
			display_screen0()
		case 1:
			display_screen1()
		case 2:
			display_screen2()
		}
	}
}

func display_loading() {
	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 20, "Loading ...")
	pcd8544.LCDdisplay()
}

func display_screen0() {
	out, err := exec.Command("/bin/screen_0.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

func display_screen1() {
	out, err := exec.Command("/bin/screen_1.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

func display_screen2() {
	out, err := exec.Command("/bin/screen_2.sh").CombinedOutput()
	if err != nil {
		out = []byte(err.Error())
	}

	pcd8544.LCDclear()
	pcd8544.LCDdrawstring(0, 0, string(out))
	pcd8544.LCDdisplay()
}

/bin/screen_2.sh

[Read More]

使用tsar记录和监控树莓派CPU温度

夏天到了,树莓派的CPU温度也开始节节攀升,虽然我们也可以用云服务cosm来监控,但每5分钟采样一次精度不够高,每分钟采样一次则上传次数又太多了点。最好的方法还是使用tsar这样的工具本地高频(如每1分钟)采样,然后再定时将5分钟的均值上传到cosm绘图。

Tsar是淘宝的一个用来收集服务器系统和应用信息的采集报告工具,如收集服务器的系统信息(cpu,mem等),以及应用数据(nginx、swift等),收集到的数据存储在服务器磁盘上,可以随时查询历史信息,也可以将数据发送到nagios报警。Tsar能够比较方便的增加模块,只需要按照tsar的要求编写数据的采集函数和展现函数,就可以把自定义的模块加入到tsar中。

更新

[2013-04-14] mod_rpi已经被合并到了主干代码:https://github.com/alibaba/tsar/blob/master/modules/mod_rpi.c 只需要增加文件:/etc/tsar/conf.d/rpi.conf,内容为以下即可开始使用mod_rpi模块:

mod_rpi on

####add it to tsar default output
output_stdio_mod mod_rpi

mod_rpi模块开发方法

首先按照安装说明,见https://github.com/alibaba/tsar将tsar和tsardevel安装好。

首先运行下面的命令生成mod_rpi模块:

hugo@raspberrypi2 ~/projects/tsardevel $ tsardevel rpi 
build:make
install:make install
uninstall:make uninstall
hugo@raspberrypi2 ~/projects/tsardevel $ ls rpi
Makefile  mod_rpi.c  mod_rpi.conf

然后修改mod_rpi.c,增加读取CPU温度的逻辑:

/*
 * (C) 2010-2011 Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "tsar.h"

/*
 * Structure for rpi infomation.
 */
struct stats_rpi {
	unsigned int cpu_temp;
};

#define STATS_TEST_SIZE (sizeof(struct stats_rpi))

static char *rpi_usage = "    --rpi               Rapsberry Pi information (CPU temprature ...)";


static void read_rpi_stats(struct module *mod, char *parameter)
{
	FILE *fp;
	char buf[64];
	memset(buf, 0, sizeof(buf));
	struct stats_rpi st_rpi;
	memset(&st_rpi, 0, sizeof(struct stats_rpi));

	if ((fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r")) == NULL) {
		return;
	}

	int cpu_temp;

	fscanf(fp, "%d", &cpu_temp);

	st_rpi.cpu_temp = cpu_temp;

	int pos = sprintf(buf, "%u",
			/* the store order is not same as read procedure */
			st_rpi.cpu_temp);
	buf[pos] = '\0';
	set_mod_record(mod, buf);
	fclose(fp);
	return;
}

static struct mod_info rpi_info[] = {
	{"  temp", SUMMARY_BIT,  0,  STATS_NULL}
};

static void set_rpi_record(struct module *mod, double st_array[],
		U_64 pre_array[], U_64 cur_array[], int inter)
{
	st_array[0] = cur_array[0]/1000.0;
}

void mod_register(struct module *mod)
{	
	register_mod_fileds(mod, "--rpi", rpi_usage, rpi_info, 1, read_rpi_stats, set_rpi_record);
}

最后make && sudo make install将mod_rpi自定义tsar模块安装好。

[Read More]