2012年1月29日 星期日

開發環境 BeagleBone A8 實測Linaro-nano

最近實測了一塊Linaro活動贈送的開發板BeagleBone Rev.A3 (PDF英文手冊開發說明)。其中,內附一個4GB的micro SD 卡。

IMG_1622 IMG_1623

SD卡已預先載好Linaro-nano (Linux linaro-nano 3.1.0+ #1 Tue Nov 15 15:51:15 CET 2011 armv7l armv7l armv7l GNU/Linux)
驅動程式在Windows XP 上有些小問題。
請依下照步驟安裝,(其他系統安裝,詳參閱原英文說明)
  • 解壓縮BONE_DRV.exe
  • 先連接usb連接線
  • 硬體安裝精靈,並手動指定解壓目錄,進行安裝USB序列埠驅動程式及FTDI虛擬COM Port驅動程式。(詳見文後05-20更新)
IMG_1624

成功安裝後,可在裝置管理員中看到下列資訊(以筆者電腦環境,為COM6)。
image

接著開啟’超級終端機’,設定如下圖示:
beaglebone_com6_setup

重新接上USB線,即可看到,Linaro 開機畫面。
beaglebone_install_openssh_server_done

預設Linaro-nano環境,非常陽春,需要手動設定網路,來安裝需要的開發工具。
ifconfig eth0 192.168.253.10 netmask 255.255.255.0
route add -net 0.0.0.0 gw 192.168.253.1
echo "nameserver 192.168.253.1" > /etc/resolv.conf

以安裝openssh-server 為例
apt-get install openssh-server

beaglebone_install_openssh_server_start ssh_connected


小結

Linaro-nano 使用gcc 4.6.1,若要利用桌機進行開發,則需使用最新版本的Ubuntu11.10 做為來cross-compile開發環境,稍有不便。
就另一方面來說,目前CPU A8,256MB 記憶體, SD卡容量達4G,足夠用以直接在實驗板上開發部分小型程式。
筆者將所需要的程式一一安裝好後,體積小的它,直接可以作為移動的ARM小型開發平台,十分方便。

更新2012-05-20
回覆Henry讀者:筆者是在Windows XP 上,安裝BONE_DRV.exe
先使用USB連接BeagleBone到主機
假設解壓目錄為 C:\temp\BONE_DRV
會出現 尋找新增硬體 (選取不,連線到windows update)-> 選取從清單或特定位置安裝
1. 控制台->新增硬體精靈
 ->選擇BONE_DRV\FTDI 目錄進行安裝->出現尚未通相容性測試警告(如下圖示)
 
-> 選取繼續安裝->完成。
2. 接著又會跳出一次來安裝 XDS100v2 compatible USB Serial Converter B, 再安裝一次即可。

2011年12月31日 星期六

阿班私房菜- 初探 Networking debug Logging (網路偵錯日誌)

tcpdump-with-cap

(註: 筆者愛用 nc(netcat) + tcpdump 來做網路實驗,上圖即其結果。 )

記得許久前,某人問我一個問題,有比較好的遠端偵錯的方法嗎?

首先,我先定義所謂"遠端偵錯":
  • 無法利用即時偵錯
    • 你的程式可能在數百公里外執行。簡言之,你沒辦法到現場去查臭蟲。
    • 24X7伺服端程式,你不能停止它來使用單步偵錯。
筆者先談談針對網路相關問題偵錯心得:

排除法:先簡化錯誤發生的因素。

案例:某公司開發用伺服器上的網站,使用手機(Android Phone)或較慢的機器(如:Eeepc),時常發生連線逾時的情形;但手機連線至他站均無問題。
  • 先檢查,區網內的硬體是否有問題?(無線Access Point (AP)及Router 檢查,甚至網路線更換,無改善。)
  • 再查,AP的DNS是否有問題。(無發現)
  • 查看google 大神,是否Eeepc 網路卡有相容性問題。(無發現)
  • 拿出無敵大絕招,使用擷取網路封包方法。(注意:要擷取封包時,請同時抓取,Server 及 Client 端。)
    • Server 遠端使用tcpdump 擷取封包並儲存為Wireshark 可開啟的檔案.cap
    • Client 端使用Wireshark來擷取封包。
    • 在Client 重現連線網路問題。
    • 比對封包 (其中發現Server端收到3個SYN 封包,卻無回應到client 端)
    • 範圍縮小至Server端
    • 利netstat –na | grep TIME_WAIT‘ , 發現TCP TIME_WAIT 很快被系統移除。
小結:

  到此階段,筆者有很高的把握,應該是系統網路優化設定所造成。

再和朋友試一下,網路相關設定才發現與tcp_tw_recycle有關;關閉後一切正常。

$ grep recycle /etc/sysctl.conf
net.ipv4.tcp_tw_recycle=1   --> net.ipv4.tcp_tw_recycle=0


結論:

對於網路程式的偵錯而言,看懂封包的協定是十分必要的,
另外在撰寫網路相關的程式,若能瞭解所使用標準協定(如:TCP TimeWait RFC793, HTTP RFC2616),將有莫大的幫助,以避免所開發程式與其它第三方程式發生不相容情事。

(有任何誤謬,請不吝指正。)


2011年12月30日 星期五

阿班私房菜-Design Pattern(DP)初探使用Python (1)


Design Patterns: Elements of Reusable Object-Oriented Software

最近使用Python 寫程式,心血來潮,使用 (DP) 套用看看。
以 Factory worker 為例。
首先建立一個物件Worker 包含姓名、屬性等等。
class Worker:
    def __init__(self, name=None):
        self.name = capfirst(name) if name else "The worker"
    def getName(self):
        return self.name
    def attrib(self):        return "Worker"
    def lookandfeel(self):
        print "%s is a %s" % (self.name, self.attrib())

Designer與Engineer繼承它,並覆寫attrib() Method。
class Designer(Worker):
    def attrib(self):        return "Designer"
class Engineer(Worker):
    def attrib(self):        return "Engineer"

當使用各自物件來建立,並執行lookandfeel() 時,會呼叫各自的attrib method。
以下使用 factory pattern 來實作僱用workers 的容器物件。
# factory pattern
class Factory:
    type_designer = 1
    type_engineer = 2
    def __init__(self):
        self.workers = []
    def employ(self, worker_type, name=None):
        if worker_type == Factory.type_designer:
            self.workers.append(Designer(name))
        elif worker_type == Factory.type_engineer:
            self.workers.append(Engineer(name))
    def workerCount(self):
        return len(self.workers)
    def browse(self):
        for a in self.workers:
            a.lookandfeel()
z = Factory()
z.employ(Factory.type_designer)
z.employ(Factory.type_designer, 'bill')
z.employ(Factory.type_engineer, 'sue')
z.browse()

執行結果:
$ python factory_dp1.py
The worker is a Designer
Bill is a Designer
Sue is a Engineer

承上篇重構一文:我們來重構一下它,並加入singleton pattern(利用@staticmethod 來實作)。
class Factory:
    # class static variable
    type_designer = "1"
    type_engineer = "2"
    _Factoryself = None
    _factorydict = {
           type_designer: Designer,
           type_engineer: Engineer,
        }
    def __init__(self):
        self.workers = []

    @staticmethod
    def singleton():
        if Factory._Factoryself == None:
            Factory._Factoryself = Factory()
        return Factory._Factoryself



    def employ(self, worker_type, name=None):
        caller = Factory._factorydict.get(worker_type)
        if caller:
            self.workers.append(caller(name))
        return None



    def workerCount(self):
        return len(self.workers)

    def browse(self):
        i = 0
        for a in self.workers:
            print " - Worker[%s]:%s" % (i, a.getLookandfeel())
            i+=1

def main():
    f = Factory.singleton()
    f2 = Factory.singleton()
    if f == f2:
        print "* Singleton pattern of Factory"



    print "* Factory pattern to employ"
    f.employ(Factory.type_designer)
    f.employ(Factory.type_designer, 'Bill')
    f.employ(Factory.type_engineer, 'Sue')
    print "* Factory pattern to browse"
    f.browse()
    print "* The factory has %s workers." % f.workerCount()

if __name__ == "__main__":
    main()

小結

使用python 來寫DP程式,功能強大,自由度高;
但這意味,寫其程式時,要有其紀律,遵守一定規範(coding convention)來寫;
不然程式會變得非常難讀及維護。 最近上列範例程式均可在github-class_dp中找到。
上述範例還可進一步,重構或修改為工廠人員管理系統的雛形,留待有興趣的讀者們來添加!

2011年11月30日 星期三

阿班私房菜-重構(Refactoring)的小技巧(1)


最近在看一本書"The Pragmatic Programmer",裡有不少與重構相關的範例,值得參考,有與趣人可以買來看看。因此書,筆者心血來潮,分享一下自己的開發經驗。


在專案開發程式的過程,常會因時間壓力或市場考量,不得不加入很多非計畫中的程式碼。
開發時間越久,就漸漸難以維護,增加新功能更為秏時;當人力不足以達成出貨時程,便開始增加人力,最後日積月累,專案程式變成一個大怪物;直至某一日砍掉重練。


而筆者近年來,使用重構方法,使得自己不用一直為解專案bug而加班。

重構(Refactoring) 英文是一個現在進行式,表示這個動作需要一直不斷發生。


其中,"重構"的最重要的基石就是自動化測試程式,如每當重構程式碼中重覆的部分時,執行一下自動化測試程式,便可很快的確認程式修改後的正確性。

接下來我們來看一些筆者常用的小技巧,以下範例使用Python語言。

def show_animal_info0(animal):
    if animal == 'dog':
        print("The dog has four legs")
    elif animal == 'chick':
        print("The chick has two legs")
    else:
        print("There is an unknown animal")
show_animal_info0("dog")
show_animal_info0("chick")
show_animal_info0("cat")


重構if-else

通常if-else會越加越長,慢慢變得難以除錯。
animal_info_dict = {
        "dog":   "four legs",
        "chick": "two legs"
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        print("The %s has %s." % (animal, info))
    except:
        print("There is unknown animal")
用dict結構表格取代if-else, 也可用來取代switch


重構重覆呼叫函式

這種函式大部分由copy and paste而來。
show_animal_info0("dog")
show_animal_info0("chick")
show_animal_info0("cat")
def main():
    animals = ["dog", "chick", "cat"]
    for name in animals:
        show_animal_info1(name)
if __name__== "__main__":
    main()

重構屬性
紅色legs部分也是重覆程式碼的狀況之一
animal_info_dict = {
        "dog":   "four legs",
        "chick": "two legs"
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        print("The %s has %s." % (animal, info))
    except:
        print("There is unknown animal")
animal_info_dict = {
        "dog":   {"leg": "four"},
        "chick": {"leg": "two"},
        "cat":   {"leg": "four"}
        }
def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        print("The %s has %s legs." % (animal, legs))
    except:
        print("There is unknown animal.")


自動化測試程式

要進行自動化測試,以本範例為例:

1. 將邏輯與資料顯示動作分離

def show_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        print("The %s has %s legs." % (animal, legs))
    except:
        print("There is unknown animal.")
def get_animal_info1(animal):
    try:
        info = animal_info_dict[animal]
        legs = info['leg']
        msg = "The %s has %s legs." % (animal, legs)
    except:
        msg = "There is unknown animal."
    return msg

2. 比對預期函式回傳值結果

使用pyunit 來實作自動測試
import unittest
from code_for_automation import *
class DefaultTestCase(unittest.TestCase):
    def testTestDog(self):
        sample="The dog has four legs."
        msg = get_animal_info1('dog')
        assert msg == sample , 'test dog message'
    def testTestChick(self):
        sample="The chick has two legs."
        msg = get_animal_info1('chick')
        assert msg == sample , 'test chick message'
    def testTestCat(self):
        sample="The cat has four legs."
        msg = get_animal_info1('cat')
        assert msg == sample , 'test cat message'
if __name__ == '__main__':
    myTestSuite = unittest.makeSuite(DefaultTestCase,'test')
    runner = unittest.TextTestRunner()
    runner.run(myTestSuite)

3. 統計測試結果
$ python 05_automation.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
以上範例程式均可由tip1下載

結論

當有了自動化測試程式之後,可提高自行重構的信心,又不用花費手動一個個輸入測試的時間。
本文雖以python為例,但觀念是相通的,其他語言也有其自動測試函式庫可供使用,歡迎不吝指教。


django i18n 實戰 (二)


續上篇django i18n 實戰,筆者作了一些小實驗,以方便中文翻譯的進行。

整段文章中文化


在前篇提到的使用django.po 來編修翻譯的方法,在此處便不太適用。
因為這類文字時常修修補補,非常不易維護。
目前使用的解決方法,使用不同locale目錄來放置不同語言版本template。
en test.html
test2.html
zh-tw test.html
test2.html

取得目錄及template名稱
from django.utils import translation
def getLocaleResourcePathName(templateName):
    cur_language = None
    try:
        cur_language = translation.get_language()
    except:
        logger.exception('getLocaleResourcePath')
    if cur_language == 'zh-tw':
        return "%s/%s" % (cur_language, templateName)
    return “en/%s”  % templateName
其中translate.get_language() ,傳會目前要求取用此頁瀏灠器所使用的語系。


在javascript中需翻譯的文字

解法1: 為將瀏灠器目前所使用的語系,存入cookie中,以便javascript實作中文化。
def setLangCookie(request):
    params = {}
    c = Context(params)
    t = loader.get_template(pageForJS)
    response = HttpResponse(t.render( c ))
    try:
        cur_language = translation.get_language()
        response.set_cookie('lang', cur_language)
    except:
        logger.exception('set_cookie')
    return response

解法2: 同上一解法為將所需要翻譯文字集中至同一檔案,依瀏灠器目前所使用的語系,代換為i18n.js所在路徑。

簡易bash腳本檔案i18n.sh


續前篇,將相關命令包裝為腳本檔以方便使用。

如: sh  i18n.sh  u
 (即更新django.po檔案,附加新增文字)

#!/bin/sh


ACTION="$1"
TARGET="$2"

usage() {
        echo "syntax: |update|compile>"
}

case $ACTION in
initial)
        django-admin.py makemessages -l zh_TW "$TARGET"
        ret=$?
;;
u*|update)
        django-admin.py makemessages -a
        ret=$?
;;
c*|compile)
        django-admin.py compilemessages
        ret=$?
;;
*)
        usage
        ret=1
;;
esac
exit $ret

備註:其中django-admin.py,如使用環境在Mac OS X 使用brew install gettext卻找不到xgettext 和 msgfmt相關檔案時,請使用下列命令產生正確連結:
 brew link gettext


2011年11月29日 星期二

django i18n 實戰

image

最近在為的上線前專案做中文化的處理,在此留下一心得記錄。
就django 中文化相關工具而言,主要是將gettext中的兩個命令:
  • xgettext包裝成django-admin.py makemessages, 用以將程式及腳本檔中所定義的Key字串取出放入至django.po
  • msgfmt包裝成django-admin.py compilemessages, 用來把django.po 轉為二進位的django.mo檔案。
Key 字串
以底線(_)為常見的gettext key字串
在下列命令為常使用的兩種:
from django.utils.translation import gettext_lazy as _
筆者使用下列語法:
from django.utils.translation import ugettext as _
為什麼筆者不使用 gettext_lazy呢?
django 在更新po檔案時,會將相近字詞直接複製到附加的詞組中,時常牛頭不對馬嘴,不如手動編修。

demo 專案為例
請進入與settings.py同層的專案目錄
$ django-admin.py makemessages –l zh_TW
processing language zh_TW
正確產生自動下列檔案django.po
locale/zh_TW/LC_MESSAGES/django.po
如出現下列錯誤:
$ django-admin.py makemessages -l zh_TW
Error: This script should be run from the Django SVN tree or your project or app tree. If you did indeed run it from the SVN checkout or your project or application, maybe you are just missing the conf/locale (in the django tree) or locale (for project and application) directory? It is not created automatically, you have to create it by hand if you want to enable i18n for your project or application.
請建立locale目錄,再試一次。

更新po檔案
每當新增一些個需要中文化字串,並不需要將它們動手加入django.po
可使用下列指令,直接將新字串附加至原有的django.po:
$ django-admin.py makemessages –a
編譯mo
$ django-admin.py compilemessages
註:在測試時,如有重新mo檔案,請重新執行django。

附錄
  • 在使用 blocktrans 及 endblocktrans時,因為採用python 內建dict 變數代換功能,所以只能使用-層。範例如下:
{% blocktrans %} {{user.username}} 進行統
統操作中,… {% endblocktrans %}
其中 {{user.username}}將無法正確代換,需改為 {{username}} 一層dict 結構。對照python 原字串展開範例:
userinfo={'username': _('Bill')}
"%(user.username)s 進行統
統操作中,…" % userinfo


結論


gettext 廣泛使用在開源軟體之中,而django-admin加以包裝,讓它在使用上更加方便。
若對如何包裝gettext有興趣,可參閱python腳本檔如下:
/usr/lib/python2.7/site-packages/django/core/management/commands/makemessages.py
/usr/lib/python2.7/site-packages/django/core/management/commands/compilemessages.py

2011年11月3日 星期四

Vagrant+virtualbox (2): 實戰封裝自制package.box

image

續上篇執行lucid32.box 使用 jRuby + vagrant在Windows XP
Vagrantfile 範例
Vagrant::Config.run do |config|
  config.vm.box = "gold"
  # config.vm.forward_port("web", 80, 8080)
end


添加所需應用程式及設定,如安裝django套件:
sudo easy_install pip
sudo pip install django
使用此檔案執行 vagrant up後,使用 putty (參考執行lucid32.box 使用 jRuby + vagrant在Windows XP


若要附加自己的說明檔案,請使用—include
vagrant package --vagrantfile .vagrant --include README.txt


若要附加自己的說明檔案,請使用--
vagrant package --vagrantfile .vagrant --


執行結果
C:\Documents and Settings\vagrant\SRC\vagrant\gold>vagrant package --vagrantfile .vagrant --
[default] Attempting graceful shutdown of linux...
[default] Clearing any previously set forwarded ports...
[default] Cleaning previously set shared folders...
[default] Creating temporary directory for export...
[default] Exporting VM...
Progress: 0%Progress: 1%Progress: 4%Progress: 9%Progress: 10%Progress: 14%Progre
ss: 19%Progress: 24%Progress: 28%Progress: 34%Progress: 39%Progress: 42%Progress
: 44%Progress: 49%Progress: 54%Progress: 69%
[default] Compressing package to: C:/Documents and Settings/vagrant/SRC/vagrant/gold/package.box[default] Packaging additional file: .vagrant


加入本機檔案package.box 為mygold box
vagrant box add mygold package.box


執行結果
C:\Documents and Settings\vagrant\SRC\vagrant\gold>vagrant box add mygold package.box[vagrant] Downloading with Vagrant::Downloaders::File...
[vagrant] Copying box to temporary location...
[vagrant] Extracting box...
[vagrant] Verifying box...
[vagrant] Cleaning up downloaded box...

在新增之後,看看目前有的boxes:
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>vagrant box list
gold
lucid32
mygold


修改新的Vagrantfile 來使用 mygold
Vagrant::Config.run do |config|
  config.vm.box = "mygold"
  # config.vm.forward_port("web", 80, 8080)
end

執行vagrant up 啟動
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>vagrant up
There is a syntax error in the following Vagrantfile. The syntax error
message is reproduced below for convenience:
C:/Documents and Settings/vagrant/.vagrant.d/boxes/mygold/include/_Vagrantfile:1: syntax error, unexpected ':'
{"active":{"default":"f5a2c996-a70b-4c2c-8928-a85127a26863"}}

發生錯誤,移除include/_Vagrantfile ,再執行一次,啟動成功。
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>vagrant up
[default] Importing base box 'mygold'...
[default] The guest additions on this VM do not match the install version of
VirtualBox! This may cause things such as forwarded ports, shared
folders, and more to not work properly. If any of those things fail on
this machine, please update the guest additions and repackage the
box.
Guest Additions Version: 4.1.2
VirtualBox Version: 4.1.4
[default] Matching MAC address for NAT networking...
[default] Clearing any previously set forwarded ports...
[default] Forwarding ports...
[default] -- ssh: 22 => 2222 (adapter 1)
[default] Creating shared folders metadata...
[default] Running any VM customizations...
[default] Booting VM...
[default] Waiting for VM to boot. This can take a few minutes.
[default] VM booted and ready for use!
[default] Mounting shared folders...
[default] -- v-root: /vagrant
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>vagrant ssh
`vagrant ssh` isn't available on the Windows platform. The
vagrant.ppk file for use with Putty is available at:
  C:/jruby-1.6.5/lib/ruby/gems/1.8/gems/vagrant-0.8.7/keys/vagrant.ppk
To use this create a new Putty session for `vagrant@localhost`
on port `2222`, in the Connection>SSH>Auth
configuration section navigate to the vagrant.ppk file,
select it, save the session for later use, and connect.
For a more detailed guide please consult:
http://vagrantup.com/docs/getting-started/setup/windows.html
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>

使用 putty連入,檢查之前在gold box所產生的測試用檔案[my.box],OK。
image


停止啟動mygold box

vagrant halt


刪除啟動後的mygold box
C:\Documents and Settings\vagrant\SRC\vagrant\mygold>vagrant destroy
[default] Forcing shutdown of VM...
[default] Destroying VM and associated drives...


結論
利用封裝已安裝及設定好的軟體的Box,可大大減少啟動後所需的安裝時間。
且可使用不同版本的gold box 來進行軟體自動化測試,並方便管理。


附註
多人共用mygold.box
可將編寫腳本檔案 mygold_update.bat 中加入如下設定:
@echo off
REM 刪除已存在的mygold_xxxx, 視情況決定是否執行destory, 或由使用者自行決定
vagrant destroy
REM 移除舊的mygold
vagrant box remove mygold
REM 加入新的mygold
vagrant box add mygold http://yourwebserver:port/mygold.box
註:請記得將所產生的package.box檔案更改並放到自行架設的web server 上。

可與ssh 搭配使用
vagrant ssh 等於
ssh -i <vagrant private key> vagrant@localhost –p <vagrant port>
(ex: ssh -i /cygdrive/c/jruby-1.6.5/lib/ruby/gems/1.8/gems/vagrant-0.8.7/keys/vagrant vagrant@localhost -p 2222)

下列為使用cygwin + ssh + vagrant key
$ ssh -i /cygdrive/c/jruby-1.6.5/lib/ruby/gems/1.8/gems/vagrant-0.8.7/keys/vagrant vagrant@localhost -p 2222
Could not create directory '/home/vagrant/.ssh'.
The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established.
ECDSA key fingerprint is 36:28:64:97:6c:a4:dd:2a:4e:1a:0e:db:3a:db:d4:66.
Are you sure you want to continue connecting (yes/no)? yes
Failed to add the host to the list of known hosts (/home/vagrant/.ssh/known_hosts).
Welcome to Ubuntu 11.04 (GNU/Linux 2.6.38-8-server x86_64)
* Documentation:  http://www.ubuntu.com/server/doc
  System information as of Thu Nov  3 01:22:58 CST 2011
  System load:  0.0               Processes:           71
  Usage of /:   3.1% of 38.15GB   Users logged in:     0
  Memory usage: 6%                IP address for eth0: 10.0.2.15
  Swap usage:   0%
  Graph this data and manage this system at https://landscape.canonical.com/
New release 'oneiric' available.
Run 'do-release-upgrade' to upgrade to it.
Last login: Thu Nov  3 00:49:19 2011 from 10.0.2.2
vagrant@vagrant-ubuntu-natty:~$