2012年2月26日 星期日

[Python] 使用 Heroku 筆記 @ Windows 7


差不多該玩一下 Heroku 了,這跟 Google App Engine 一樣,是一套 PaaS 服務架構,仔細看才發現 Heroku 竟然是架設在 EC2 上面的服務,讓我想起 Dropbox 這有名的服務是使用 S3 架構,原來有不少成功的服務不見得從機房都自己打造呢!這樣的感觸滿特別的,不需堅持自己從零開始。使用 Heroku 跟 GAE 這類服務的主因之一,是想要妥善利用身邊的資源,不見得要去租一台虛擬機器來使用,雖然權限大能做的事很多,但一開始太多資源不見得是好事,除此之外,使用 Heroku 跟 GAE 的好處是碰到流量大時,錢砸下去就可以 Scaling ,這樣的投資似乎不差吧?最大的缺點是要去習慣這些架構,轉個角度來看,或許這就是雲端的成本吧?就像把想做的事寫成符合 Hadoop/HBase 架構一樣。


對 Heroku 來說,計費方式是以 process 單位小時的方式計價,例如免費帳號就是一個月使用 750 小時不用錢,使用一隻 process 一個月是 720 小時,所以就不用錢。原先是一隻 process 用一小時 0.05 美元,其中 process 分為 web process 跟 background process 兩種。只是 Heroku 資料庫免費空間僅 5MB 而已,像 GAE 則是 1GB 大小,但是 GAE 還有計算 in/out 的資料量等等。我還沒搞懂 Heroku 有沒計算流量以及是否有限制 library 的部分 :P


好啦,回過頭講一下在 Windows 的操作方式,準備資源:



依序安裝完 Heroku Windows、Python 2.7 後,把 virtualenv-1.7.1.2.tar.gz 解壓縮後,就可以準備操作了,主要參考 Heroku - Getting Started with Python on Heroku/Cedar 這篇,先用最簡單的方式體驗一下,其中有用到 python Flask,這是一個輕量型 MVC 的 framework 囉。


步驟:


Python 2.7 在 C:\Python27\python.exe;virtualenv 在 C:\virtualenv-1.7.1.2;heroku-toolbelt 預設在 C:\Program Files\Heroku。安裝完軟體後,直接用 cmd 運作即可。


建立空目錄,如 helloflask


C:\Users\user>mkdir helloflask && cd helloflask
C:\Users\user\helloflask>


建立 virtualenv 環境


C:\Users\user\helloflask>C:\Python27\python.exe C:\virtualenv-1.7.1.2\virtualenv.py venv --distribute
New python executable in venv\Scripts\python.exe
Installing distribute...done.
Installing pip...done.


啟用虛擬環境:


C:\Users\user\helloflask>venv\Scripts\activate
(venv) C:\Users\user\helloflask>


安裝 Flask:


(venv) C:\Users\user\helloflask>pip install flask


Downloading/unpacking flask
Downloading Flask-0.8.tar.gz (494Kb): 494Kb downloaded
Running setup.py egg_info for package flask
...
Downloading/unpacking Werkzeug>=0.6.1 (from flask)
Downloading Werkzeug-0.8.3.tar.gz (1.1Mb): 1.1Mb downloaded
Running setup.py egg_info for package Werkzeug
...
Downloading/unpacking Jinja2>=2.4 (from flask)
Downloading Jinja2-2.6.tar.gz (389Kb): 389Kb downloaded
Running setup.py egg_info for package Jinja2
...
Successfully installed flask Werkzeug Jinja2
Cleaning up...


建立 app.py 程式(C:\Users\user\helloflask\app.py):


import os


from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello():
        return 'Hello World!'


if __name__ == '__main__':
        # Bind to PORT if defined, otherwise default to 5000.
        port = int(os.environ.get('PORT', 5000))
        app.run(host='0.0.0.0', port=port)


建立 Heroku 環境檔案:


(venv) C:\Users\user\helloflask>pip freeze > requirements.txt
(venv) C:\Users\user\helloflask>cat requirements.txt
Flask==0.8
Jinja2==2.6
Werkzeug==0.8.3
distribute==0.6.24
(venv) C:\Users\user\helloflask>echo web: python app.py > Procfile
(venv) C:\Users\user\helloflask>cat Procfile
web: python app.py


執行 web service:


(venv) C:\Users\user\helloflask>foreman start


接著就可以用瀏覽器瀏覽 http://localhost:5000 啦,可以看到印有 "Hello World!" 字樣,要結束的話就按 ctrl+c 囉


如此一來,就完成本地端的測試開發囉。至於 Deploy 的部分,則是好好看一下官網上面的介紹,這邊就不多提了。


整體上,覺得 GAE 比較簡單一點,過程中只需安裝 python 跟 GAE SDK 環境,但 Heroku 還須使用 Ruby 環境等,或許對已習慣用 Ruby 的人來說,是非常自在的 :D


2012年2月24日 星期五

行動化

筆電與麥當勞


越來越習慣帶著筆電的生活,有沒網路倒還好,沒資源有沒資源可以把玩的事,看看很久沒翻的電子書,不然就在本機端練習一下 GAE 也好(python跟GAE都有離線文件),快不快活只是一個角度的差別。有網路時就把玩網路,換個環境其實還不錯的,有聲響也不會打亂寫程式的步調,有人影也能輕鬆望個幾眼,麻煩的大概是上廁所也要把東西帶在身上,以及走路背著幾公斤的重訓。我想,也到了該認真投資一台好筆電的時刻了吧?


回顧一下,兩個月左右,接觸了硬體界的朋友,才發現硬體界似乎不是我想走的方向,這邊一樣高深莫測,但我開始比較清楚自己想要的是甚麼東西了,剛好幾天前跟 IC 界的聊天,他說目前 IC 廠很缺上層玩 ap 的人,只是十年了,玩上層 ap 仍被認為是一件簡單的事,彷彿純軟的、沒硬體成本的容易不被重視。這讓我想起工作上曾跟硬體界的朋友開會,他一頭就劈做 xxx 的有啥難的,不就想一下演算法就解掉了嗎?說真的我無法反駁,我無法證明問題的難易度,這不像硬體可以用看的、摸的、花錢的等等的估算。


無論如何,還是要珍惜手邊的資源,把能學能掙的要盡量把握,深信某一天某一刻一定會派上用場的 :)


2012年2月23日 星期四

Android 開發教學筆記 - 研究 Renderscript 以 Carousel 為例 @ PandaBoard


這個 Carousel 例子是一個類似翻頁動作的特效,仔細看一下,是不是覺得自己在一個圓心中間,被很多照片包圍住呢?這個例子是 10 張照片,等於你走進了一個 10 面牆的房間,隨著你觸動移動事件,等於房間每面牆近似以你為中心旋轉。猜測原理後,就可以去驗證實做了。


six
此圖為正六邊形。左下角用來推邊長 len,右上角用來推各點 P1, P2, ... 的座標,θ就是兩頂點的夾角,此外,定義右下角是第一象限,左下是第二。


首先,讓我驗證這是個正 n 邊形的效果,主要是看到這段程式碼:


@ initBitmaps(): // Calculate the length of the polygon
len = RADIUS * 2 * sin(M_PI/NUM_ITEMS);


這是計算正 n 邊形的邊長公式,其中 n = NUM_ITEMS,此例就是 10 張照片


接著,開始計算正 n 邊形各頂點的三維座標資訊:


此例是把 y 軸當作 0 來看代 (x,y,z) = ( sinθ * RADIUS, 0, -cosθ * RADIUS)


@ initBitmaps(): // Calculate the vertices of rectangles
vertices[i*3] = sin(angle * M_PI / 180) * RADIUS;
vertices[i*3 + 1] = 0;
vertices[i*3 + 2] = -cos(angle * M_PI / 180) * RADIUS;


畫出正 n 邊形:


@ displayCarousel(): // Draw the rectangles
for (int i = 0; i < 10; i++) {
        ...
        rsgDrawQuadTexCoords(...);
        ...
}


rsgDrawQuadTexCoords 則是輸入長方形四個座標點,但是每一個頂點傳入的參數還有多了兩個,該數值通常定義為 (u, v),其中 u = 0 代表左邊,反之右邊,而 v =0 代表上面,反之下面,因此此例依序透過 (u, v) = (0, 1), (0, 0), (1, 0), (1, 1)來描述頂點位置。其中 (0, 1) 和 (0, 0) 使用的頂點座標位置分別是 ( r*sinθ , -(len/2), -r*cosθ ) 和 ( r*sinθ , len/2, -r*cosθ ),還記得最初定義得座標系嗎? y 軸是縱軸,此例第一張照片左上角座標為 ( r*sinθ , len/2, -r*cosθ ),左下角為 ( r*sinθ , -(len/2), -r*cosθ ),至於第一張圖的右上角跟右下角得座標,那就是用正 n 邊形第二個頂點來計算,以此規則算出 10 張照片的座標位置


至於觸動時的旋轉效果,則是透過 rotate 的功能實作的:


@ displayCarousel():
// Load vertex matrix as model matrix
rs_matrix4x4 matrix;
rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f); // camera position
rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f); // camera rotation
rsgProgramVertexLoadModelMatrix(&matrix);


先把 camera(觀看照片的位置?) 位置移到 (0,0,-400) 位置,由於此正 n 邊形半徑為 828,因此距離照片 428 各單位。接著讓 camera 的位置對 (0,1,0) 向量旋轉,就達成這個效果囉


其他資訊:


rsMatrixLoadPerspective 對應 OpenGL:gluPerspective,以 void gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); 來說,分別為視角、寬高(長)比、前景和背景,查 wiki 可知人的視角大約 120 度,魚眼是 180 度,在此例是 30 度視角為例,接著寬高(長)比沒啥問題,而 near 和 far 的參數,若有玩單眼相機的話,應該不會太陌生,在網路上找資料的結果,通常前景是用一個大於 0 的數值,如 0.1f 等。


rsMatrixLoadTranslate 對應 OpenGL: glTranslate,以 void glTranslated(GLdouble x, GLdouble y, GLdouble z); 來說,就是切換到 (x,y,z) 座標


rsMatrixRotate 對應 OpenGL: glRotate,以 void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); 來說,代表以 (x,y,z) 向量為軸心,旋轉 angle 弧度。此外,(0,1,0) 和 (0,100,0) 都是一樣的向量,網路資料是說用 (0,1,0) 所造成的運算量較低。


而 rsMatrixLoadTranslate 和 rsMatrixRotate 定義可在 Renderscript Reference 查詢。


2012年2月19日 星期日

[Python] 使用 Google App Engine 之資料模型(Data Model)筆記 @ Windows 7


操作簡單的 GAE 後,開始來摸摸資料儲存的部分。對於 GAE DB 的部分,操作不難,跟 Django 很像,先定義一個 Data Model (資料庫的資料表),接著就可以操作了!此例僅簡單帶過,更豐富的操作方式請參考官網 GAE - 資料模型


建立 NewsData 資料模型:


from google.appengine.ext import db


class NewsData(db.Model):
        check = db.StringProperty()
        url = db.StringProperty()
        title = db.StringProperty()
        date = db.DateProperty()


新增一筆資料:


import datetime
item = NewsData(chech='1',url='http://localhost',title='TestNews',date=datetime.datetime.now().date())
item.put()


查詢資料:


使用 Data Model 查詢:


q = NewsData.all()
results = q.fetch(3)
for p in results:
        print '<a href="%s">%s</a>' % (p.url,p.title)


使用 GqlQuery 之 SQL 語法:


# 查詢 3 天內的新聞
q = db.GqlQuery("SELECT * FROM NewsData WHERE date > :1 ORDER BY date DESC", datetime.datetime.now().date() - datetime.timedelta(days=3) )
results = q.fetch(3)
for p in results:
        print '<a href="%s">%s</a>' % (p.url,p.title)


上述都很淺顯易懂,接著能嘗試簡易的 MVC 架構,把 DB Modle 定義在 mydb.py 檔,由 myput.py 和 myquery.py 作為 CGI 來操作(MVC的 VC 偷懶合在一起 XD)。


目錄結構:


app.yaml
favicon.ico
index.yaml
main.py
mydb.py
myquery.py
myput.py


app.yaml:


application: engineapp
version: 1
runtime: python
api_version: 1


handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico


- url: /query
  script: myquery.py


- url: /put
  script: myput.py


- url: .*
  script: main.py


mydb.py:


from google.appengine.ext import db
class NewsData(db.Model):
        check = db.StringProperty()
        url = db.StringProperty()
        title = db.StringProperty()
        date = db.DateProperty(auto_now_add=True)


myput.py:


# -*- coding: utf-8 -*-
print 'Content-Type: text/html'
print ''


import mydb
import cgi, hashlib, datetime, urllib
from google.appengine.ext import db


request = cgi.FieldStorage()


newsDate = datetime.datetime.now().date()
newsTitle = 'defaultTitle' if request is None or 'title' not in request or request['title'].value == '' else cgi.escape(request['title'].value)
newsURL = 'http://localhost' if request is None or 'url' not in request or request['url'].value == '' else request['url'].value
newsCheck = hashlib.md5(str(newsTitle)+str(newsURL)).hexdigest()


if mydb.NewsData.all().filter('check =',newsCheck).get() is None:
        item = mydb.NewsData(check=newsCheck,url=unicode(newsURL,'utf-8'),title=unicode(newsTitle,'utf-8'),date=newsDate)
        item.put()
        print 'Put'
else:
        print 'No Operation'


myquery.py:


# -*- coding: utf-8 -*-
print 'Content-Type: text/html'
print ''


import mydb
import datetime
from google.appengine.ext import db


print """
<html>
        <head>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        </head>
        <body>
"""


q = db.GqlQuery("SELECT * FROM NewsData WHERE date > :1 ORDER BY date DESC", datetime.datetime.now().date() - datetime.timedelta(days=3) )
results = q.fetch(50)
for p in results:
        url = p.url
        date = p.date
        check = p.check
        title = p.title
        print '<a href="%s">[%s] %s(%s)</a><br />' % ( url.encode('utf-8'), date, title.encode('utf-8'), check.encode('utf-8') )


print """
        </body>
</html>
"""


如此一來,透過瀏覽 http://localhost:port/put (或 http://localhost:port/put?title=123&url=www.google.com ) 新增資料,透過 http://localhost:port/query 顯示資料。除此之外,還可以透過 Google App Engine Launcher 的 SDK Console ,直接用瀏覽器去查看資料庫的東西,實在方便:


GAE11


這邊容易碰到的問題是 DB 內資料的編碼問題,我在 myput.py 把 CGI 得到的東西用 unicode(data,'utf-8') 的存進資料庫,在 myquery.py 時,則是用 data.encode('utf-8') 處理印出的部分。


2012年2月18日 星期六

[Python] 使用 Google App Engine (GAE) 筆記 @ Windows 7


很久以前就註冊了帳號,但一直都沒認真使用 :P 而後 GAE 又多了 Cron Jobs,我還是沒有用。最近提起勁來用一下吧!此篇只著重在 local 端的 python 練習。


練習的功能:



  • 設定 CLI 相容環境(command mode 可以測試 script.py 而不用每次都從瀏覽器執行)

  • 使用 urllib2 取得網路資料

  • 使用 re 取得資料(Regular Expression 處理字串)

  • 使用 urlfetch 存取網路服務(google.appengine.api) 

  • 實現 urlfetch with cookie 功能

  • 使用 plurk api 發布消息


流程:


先在 Windows 7 環境上,使用 googleappengine lib 寫寫 python 程式,接著從某個 url 取得資料,再用 re 處理字串,然後使用 plurk api 發布訊息。上述流程沒問題後,改成 CGI 模式,因此可透過 GAE 來執行,最後設定 cron jobs


安裝 GAE 相關開發環境:



  • GoogleAppEngine-1.6.2.msi

  • python-2.5.msi

  • npp.5.9.8.Installer.exe

  • google-appengine-docs-20120131.zip (GAE離線文件)


一切都用預設安裝,別忘了設定環境變數,在 cmd mode 下才可以直接打 python 來做事


GAE00


建立一個 GAE Project:


僅本地端,除了 Project 位置外(D:\GAE\workspace\engineapp),全部都預設,弄完就開起來測試一下,理論上應該可以輕易地用瀏覽器瀏覽這 hello world 程式


GAE01 GAE02 GAE03 GAE04 GAE05


新增一支 script (plurk.py):


在 Project 位置建立一個 plurk.py 空檔案,接著就切到 command mode 來測試:


C:\> D:
D:\> cd GAE\workspace\engineapp
D:\GAE\workspace\engineapp>python plurk.py
(...沒東西...) 


設置 script 可使用 GAE Libs:


# -*- coding: utf-8 -*-
"""
# ImportError: No module named google.appengine.api
import sys, os
DIR_PATH = 'C:\Program Files\Google\google_appengine'
EXTRA_PATHS = [
DIR_PATH,
        os.path.join(DIR_PATH, 'lib', 'antlr3'),
        os.path.join(DIR_PATH, 'lib', 'django'),
        os.path.join(DIR_PATH, 'lib', 'django_0_96','django'),
        os.path.join(DIR_PATH, 'lib', 'django_1_2','django'),
        os.path.join(DIR_PATH, 'lib', 'django_1_3','django'),
        os.path.join(DIR_PATH, 'lib', 'simplejson'),
        os.path.join(DIR_PATH, 'lib', 'fancy_urllib'),
        os.path.join(DIR_PATH, 'lib', 'ipaddr'),
        os.path.join(DIR_PATH, 'lib', 'webob'),
        os.path.join(DIR_PATH, 'lib', 'yaml', 'lib'),
]
sys.path = EXTRA_PATHS + sys.path


# AssertionError: No api proxy found for service "urlfetch"
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore_file_stub
from google.appengine.api import mail_stub
from google.appengine.api import urlfetch_stub
from google.appengine.api import user_service_stub


APP_ID = u'test_app'
#os.environ['AUTH_DOMAIN'] = AUTH_DOMAIN # gmail.com
#os.environ['USER_EMAIL'] = LOGGED_IN_USER # account@gmail.com


apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()
# Use a fresh stub datastore.
stub = datastore_file_stub.DatastoreFileStub(APP_ID, '/dev/null', '/dev/null')
apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)
# Use a fresh stub UserService.
apiproxy_stub_map.apiproxy.RegisterStub('user',user_service_stub.UserServiceStub())
# Use a fresh urlfetch stub.
apiproxy_stub_map.apiproxy.RegisterStub('urlfetch', urlfetch_stub.URLFetchServiceStub())
# Use a fresh mail stub.
apiproxy_stub_map.apiproxy.RegisterStub('mail', mail_stub.MailServiceStub())
"""


把這一段擺在 plurk.py 的最上面,如此一來,當你是在 command mode 上執行時,把這段註解打開即可使用(把一開始跟最後的 """ 去掉即可)


 接著撰寫 Plurk API 要用的範例程式:


 參考 [PHP] 使用官方 Plurk API 實作簡單的機器人 - 靠機器人救 Karma!以 Yahoo News 為例 架構,改成 GAE Python 版,分別實作三個主要 function:




    • def getNews()


      • 取得新聞


    • def doAct()


      • 執行 url/api 


    • def getTinyURL(src)


      • 取得縮網址




程式碼:


# return responseContent
def doAct( targetURL, method='POST', data={}, cookie = None, header=None ):
        rawHeader = {'User-Agent':'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'}
        if header:
        rawHeader = header


        # load cookie
        try:
                if cookie <> None:
                cookieData = '' # {}
                for c in cookie.values():
                        #cookieData[c.key]=c.value
                        cookieData += c.key + "=" + c.value
                if len(cookieData) > 0 :
                        rawHeader['Cookie'] = cookieData
        except Excetion, e:
                pass


        # post data
        try:
                if data and len(data) > 0 :
                        data = urllib.urlencode( data )
                else:
                        data = None
        except Excetion, e:
                data = None

        rawMehtod = urlfetch.POST if method != 'GET' else urlfetch.GET
        response = urlfetch.fetch(url=targetURL,payload=data,method=rawMehtod,headers=rawHeader,deadline=10)

        if cookie <> None:
                cookie.load(response.headers.get('set-cookie', ''))
        return response.content if response <> None and response.content <> None else ''


# return {'status':'ok','data':[ {'title':NewsTitle, 'url':NewsURL } , ... ]}
def getNews():
        out = {'status':'fail','data':None}
        try:
                # setting
                newsURL = 'http://tw.yahoo.com/'
                newsPatternBeginChecker = '<label class="img-border clearfix">'
                newsPatternEndChecker = '<ol class="newsad clearfix">'
                newsRePatternNewsExtract = '<h3[^>]*>[^<]*<a href="(.*?)"[^>]*>(.*?)</a></h3>'
                #newsRePatternNewsExtract = r'<h3[^>]*>[^<]*<a href="(?P<url>.*?)"[^>]*>(?P<title>.*?)</a></h3>'
                newsReOptionsNewsExtract = re.DOTALL
                newsPatternURLChecker = 'http:'


                raw = urllib2.urlopen(newsURL).read()
                checker = str(raw).find(newsPatternBeginChecker)
                if checker < 0:
                        out['data'] = 'newsPatternBeginChecker fail'
                        return out
                raw = raw[checker+len(newsPatternBeginChecker):]
                checker = raw.find(newsPatternEndChecker)
                if checker < 0:
                        out['data'] = 'newsPatternEndChecker fail'
                        return out
                raw = raw[:checker]
                #print "##",raw,"##"
                m = re.findall( newsRePatternNewsExtract, raw, newsReOptionsNewsExtract )
                if m:
                        out['data'] = []
                        for data in m:
                                urlChecker = data[0].find(newsPatternURLChecker)
                                if urlChecker >= 0:
                                        out['data'].append( {'title':data[1],'url':data[0][urlChecker:]} )
                                if len(out['data']) > 0:
                                        out['status'] = 'ok'
                                else:
                                        out['data'] = 'not found'
        except Exception, e:
                out['data'] = str(e)
        return out


# return short url via tinyurl.com
def getTinyURL(src):
        try:
                raw = urllib2.urlopen('http://tinyurl.com/api-create.php?'+urllib.urlencode({'url':src})).read()
                return raw.strip()
        except Exception, e:
                pass
        return None


呼叫方式:


# main
plurkAPIKey = 'YourPlurkAPIKey'
plurkID = 'YourPlurkID'
plurkPasswd = 'YourPlurkPassword'
getNewsData = getNews()


runLog = []
if getNewsData['status'] == 'ok':
        # try login
        baseCookie = Cookie.SimpleCookie()
        loginData = {'api_key':plurkAPIKey,'username':plurkID,'password':plurkPasswd}
        checkLogin = doAct( 'http://www.plurk.com/API/Users/login', 'POST', loginData, baseCookie )
        try:
                obj = simplejson.loads(checkLogin)
                if 'error_text' in obj:
                        runLog.append( 'login error: '+str(obj['error_text']) )
        except Exception,e :
                runLog.append( 'login exception: '+str(e) )
        if len(runLog) == 0:
                # try post
                for news_info in getNewsData['data']:
                        formated_message = '[News] '+news_info['url']+' ('+news_info['title']+')'
                        if len(formated_message) > 140:
                                shortURL = getTinyURL(news_info['url'])
                                if shortURL <> None:
                                        formated_message = '[News] '+shortURL+' ('+news_info['title']+')'

                        if len(formated_message) <= 140:
                                writeData = {'api_key':plurkAPIKey,'qualifier':'shares','content':formated_message}
                                checkPost = doAct( 'http://www.plurk.com/API/Timeline/plurkAdd' , 'POST' , writeData, baseCookie )

                                try:
                                        obj = simplejson.loads(checkPost)
                                        if 'error_text' in obj and obj['error_text'] <> None:
                                                runLog.append( 'post error: '+str(obj['error_text'])+', Message:'+formated_message )
                                except Exception, e:
                                        runLog.append( 'post exception: '+str(e)+', Message:'+formated_message )
        else:
                runLog.append( 'getNews error:'+getNewsData['data'])


若單純測試 Plurk API 的話,只要依序執行這兩段即可:


 # try login
baseCookie = Cookie.SimpleCookie()
loginData = {'api_key':plurkAPIKey,'username':plurkID,'password':plurkPasswd}
print doAct( 'http://www.plurk.com/API/Users/login', 'POST', loginData, baseCookie )
# try post
writeData = {'api_key':plurkAPIKey,'qualifier':'shares','content':'Hello World'}
print doAct( 'http://www.plurk.com/API/Timeline/plurkAdd' , 'POST' , writeData, baseCookie ) 


 弄成 CGI 模式:


# ...依序把上面的程式碼都湊在一起後,接著下面這段...


# CGI FORMAT for HTML
print 'Content-Type: text/html'
print ''
# report
print '<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"/></head><body>'
if len(runLog) > 0:
        print "<pre>"
        #print runLog
        print "</pre>"
        for err in runLog:
                print '<p>'+ err + '</p>'
else:
        print 'OK'
print '</body></html>'


 如此一來,就是相容於瀏覽器跟 command mode 的環境:


此為連續執行 2~3 次的結果,因為 Plurk 會擋重複訊息,所以第二次輸出的結果不一樣


GAE06


設定 GAE Project (engineapp):


 設定對應的 URL 位置:


engineapp\app.yaml:


application: engineapp
version: 1
runtime: python
api_version: 1


handlers:
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon\.ico


- url: /PlrukPost
script: plurk.py


- url: .*
script: main.py


如此就可以用網頁瀏覽:


GAE07


設定 Cron Jobs:


engineapp\cron.yaml:


cron:
- description: news job
url: /PlurkPost
schedule: every 20 minutes


這樣每 20 分鐘就會去瀏覽該網頁一次,自然就會執行工作一次,此部分需要上傳到 GAE 上才能


GAE08


Deploy 筆記:


在 Google App Engine 的文件上,以 Python 2.5 為例,然而在使用 Deploy 時,會看到 ssl module not found 的訊息,因此無法上傳到 server。最簡單的解法就是安裝一下 Python 2.7 版,接著在 Google App Engine Launcher -> Edit -> Preferences 指定 Python Path ,就能夠順利上傳囉!


GAE09


查詢無線網路 AP 密碼 @ Windows 7

wifi-check-passwd wifi-check-passwd


不小心忘記無線 AP 密碼,所幸對 Windows 7 來說很容易查到,筆記兩張圖就行了。無線網路連線 -> 指定 wifi ap 右鍵"內容" -> 安全性 -> 顯示字元。


2012年2月15日 星期三

Android 開發教學筆記 - 初探 Renderscript 以 HelloCompute 為例 @ PandaBoard

tw.study.rs_screenout


最近把玩 Renderscript(RS),這東西在 2011 年初被提了出來,但最近才仔細看文件,也隨著 Android 4 可以看到完整的系統實現原始碼等,目前 RS 可用在 Android 3.x 和 Android 4.x 系統上。RS 的本意著重在三個層面,依序是 "Portability" 、"Performance" 和 "Usability"。我把他想成建立一個框架,讓你寫程式可以不用擔心硬體狀況、又不用擔心 Java VM 拖速度並且開發上可以簡單快速,當然,連帶的缺點就是必須學習這個框架的用法等。除此之外,RS 用的語法是 C 語言(C99 standard),流程看似與 JNI/NDK 很接近,但最特別的是整個架構的設計,並透過 llvm 技術,讓你的程式不只跑在 CPU 上頭,還可以跑在 GPU 或 DSP 上,目標就是提供跨硬體設備的機制,包含不同架構的 CPU 等,這就不見得單純用 JNI/NDK 可以作到的事。最後,關於 RS 的使用時機?可以用在大量運算(平行運算?)上,大部分的是用2D/3D影像處理當作例子,對於遊戲開發應該有不少幫助。


此練習仿造 Android 4.0 之 HelloCompute 範例,此範例是將一張 JPEG 圖片進行灰階影像特效。由於程式碼精簡,所以就拿來當作第一個 RS 的練習,可熟悉 RS 流程。


主要流程:


建立 Android Project -> 建立 RenderScript -> 設定使用 RenderScript 的時機。


建立 Android Project:


Project Name: StudyRSCompute
Build Target: Android 4.0
Package Name: tw.study.rs 


tw.study.rs


建立 RenderScript (MyRSCompute.rs):


src/tw.sutdy.rs -> New -> File -> MyRSCompute.rs


#pragma version(1)
#pragma rs java_package_name(tw.study.rs)


const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};


void root(const uchar4 *v_in, uchar4 *v_out) {
    float4 f4 = rsUnpackColor8888(*v_in);
    float3 mono = dot(f4.rgb, gMonoMult);
    *v_out = rsPackColorTo8888(mono);
}


按下 build 後,可以在 res->raw 看到 myrscompute.bc,另外,在 gen->tw.study.rs 也能看到 ScriptC_MyRSCompute.java 和 MyRSCompute.d 的產生,並且在 R.java 中可以看到 raw 裡頭有 myrscompute 的定義。


建立測試圖檔:


接著找一張圖,此例用 http://zh.wikipedia.org/wiki/Android 裡的圖片 http://upload.wikimedia.org/wikipedia/commons/thumb/a/ad/Galaxy_Nexus_smartphone.jpg/371px-Galaxy_Nexus_smartphone.jpg,更名為 android.jpg


在 res 中,建立 drawable 目錄,並將 android.png 擺入且 build 後,在 gen->tw.study.rs->R.java 中可以看到圖檔的定義


建立圖檔 layout (res->layout->main.xml):


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <ImageView
        android:id="@+id/displayin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


    <ImageView
        android:id="@+id/displayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />


</LinearLayout>


撰寫呼叫 RS 的時機 (StudyRSComputeActivity.java):


package tw.study.rs;


import android.app.Activity;
import android.os.Bundle;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.widget.ImageView;


public class StudyRSComputeActivity extends Activity {
    private Bitmap mBitmapIn;
    private Bitmap mBitmapOut;


    private RenderScript mRS;
    private Allocation mInAllocation;
    private Allocation mOutAllocation;
    private ScriptC_MyRSCompute mScript;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mBitmapIn = loadBitmap(R.drawable.android);
        mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(), mBitmapIn.getConfig());


        ImageView in = (ImageView) findViewById(R.id.displayin);
        in.setImageBitmap(mBitmapIn);


        ImageView out = (ImageView) findViewById(R.id.displayout);
        out.setImageBitmap(mBitmapOut);


        createScript();
    }

    private void createScript() {
        mRS = RenderScript.create(this);


        mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
        Allocation.MipmapControl.MIPMAP_NONE,
        Allocation.USAGE_SCRIPT);
        mOutAllocation = Allocation.createTyped(mRS, mInAllocation.getType());


        mScript = new ScriptC_MyRSCompute(mRS, getResources(), R.raw.myrscompute);


        mScript.forEach_root(mInAllocation, mOutAllocation);
        mOutAllocation.copyTo(mBitmapOut);
    }

    private Bitmap loadBitmap(int resource) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeResource(getResources(), resource, options);
    }
}


 整個 Package PackageExplore:


PackageExplore_tw_study_rs


猜想的架構:


在 MyRSCompute.rs 中,只有一個 root 函數,該函數接收單位是一個像素資訊(4個浮點數),並且把收到的像素 v_in 作灰階,經向量 dot 處理後,將結果存在 v_out;在 StudyRSComputeActivity 中,在 onCreate 時,把預備好的圖檔讀進來,並轉成 Bitmap 結構,隨即透過 createScript 呼叫 RS,在 createScript 中,先建立 Renderscript 執行環境,包括用 Allocation 進行 Java 層變數的初始化,這些變數之後才可以傳給 RS 用,接著在 Java 層用 forEach_root 的方式呼叫 RS 進行處理(平行處理?),最後再把處理完的資料在存回 Bitmap 變數,在 ImageView 顯示出來。


其他心得:


學會處理圖片灰階特效,也才發現圖片特效有平行處理的可行性架構。RS 有提供很多數學函數,請參考 RS Runtime API Reference


2012年2月13日 星期一

[VIM] 清除預設 vimrc 和 plugin 設定

與別人用同一個帳號共用主機時,最怕對方把 vim 設定的漂漂亮亮,華麗到我動個鍵盤就會卡了一下 Orz 為了不要動到別人的設定,請教高手同學後,剛好同學之前也有在測試 vim 設定,所以學會用 -u 指令來清掉且切換 vimrc 設定檔,接著還可以用 --noplugin 取消一堆 plugins 等等,一整個介面就清爽起來啦!


最後就設定一下 my.cshrc ,登入後 source 一下,就爽快一下。


$ vim my.cshrc


alias vim       'vim -u /path/my.vimrc --noplugin -N' 


$ vim /path/my.vimrc


autocmd FileType php set omnifunc=phpcomplete#CompletePHP
if has("multi_byte")
  set encoding=utf-8
  set fileencodings=big5,cp950,utf-8,gbk,cp936,iso-2022-jp,sjis,euc-jp,japan,euc-kr,ucs-bom,utf-bom,latin1,iso8859-1
  set termencoding=big5
  set fileencoding=big5
" for UTF-8 environment
" set termencoding=utf-8
" set fileencoding=utf-8
else
  set enc=taiwan
  set fileencoding=taiwan
endif

syntax on
set nu
set hls 


 如此一來,登入機器後,用 $ source /path/my.cshrc 後,從此 vim 設定就依照 /path/my.vimrc 的方式啦。


2012年2月11日 星期六

PandaBoard 教學筆記 - 安裝 Android 4.0 ICS via VirtualBox @ Ubuntu 10.04

Android ICS


玩了幾天 BeagleBoard-xM(Rev.C) 後,開始切換到 PandaBoard(Rev.B) 啦,從硬體規格來看,PandaBoard 好上許多,多了內建無線網卡外,CPU 是 Dual-core ARM® Cortex™-A9 MPCore™ with Symmetric Multiprocessing (SMP) at 1.2 GHz each,比 BeagleBoard-xM 的 ARM® Cortex™-A8 1.0GHz 好不少,跑 Android ICS 可以順多了!另外,最大的好處是安裝 Android 流程方便,編譯步驟在 AOSP 都有提到,也不需像 BeagleBoard 透過過於繁複的指令來處理 SD 卡,就像惡搞 Android 手機一樣,採用 fastboot 指令來處理,十分方便,缺點大概會讓入門者少學到 SD 卡處理的事情。


以下是操作環境:


OS:Ubuntu 10.04 64-Bit Desktop (主要是在 VirtualBox 裡,Guest OS 跟 Host OS 都一樣)
Target:Panda Board ES Rev. B1 + 4GB microSD


在 VirtualBox 環境碰到的問題:


板子偵測時有時無?


由於採用 VirtualBox 時,必須手動把 USB 裝置附加上去,因此需留意有那個項目可以使用,然而在 VirtualBox 的視窗上,一下可以看到板子,一下又找不到板子,這樣可好了,對我初學的我開始懷疑是線壞了還是板子壞了,在網路上搜尋的結果大多是說板子的power不夠,像是要用 Y 型 USB 接線,或是外接 5V 電源,但這兩樣我早就做好了,情況還是一樣,並可以在 /var/log/messages 看到 USB 一下接上,一下又斷掉。


$ tail -f /var/log/messages
...
kernel: [   21.750370] usb 1-1: new high speed USB device using ehci_hcd and address 8
kernel: [   21.952310] usb 1-1: configuration #1 chosen from 1 choice
kernel: [   24.440886] usb 1-1: USB disconnect, address 8
kernel: [   25.730824] usb 1-1: new high speed USB device using ehci_hcd and address 9
kernel: [   25.953153] usb 1-1: configuration #1 chosen from 1 choice
kernel: [   28.431670] usb 1-1: USB disconnect, address 9
...


可能現象及處理方式:


對非使用 VirtualBox 環境者,並不需要處理他,我猜是硬體的設計吧?讓此硬體處於可被偵測狀態,但不知為何理由要不斷斷線(省電?等待指令?),無論如何,對於 VirtualBox 使用者而言,需要小作處理,讓板子可以自動附加到 VM 中,不然一直靠手動就糗大了。首先在 VirtualBox 的 VM 開啟前先作設定 USB 相關設定,趁可以偵測到時,把他弄成自動附加即可。


PandaBoard@VirtualBox


Texas Instruments OMAP4440-USB 篩選器詳細資料


其他:


記得把目前得使用者加到 vboxusers group 裡喔 (/etc/group vboxusers:x:XXX:YourHostAccount)


主要流程:


編譯 Android ICS:


大致上流程如 AOSP 所提的環境及編法,只是在編譯前,要先安置好 PandaBoard 所需的硬體資訊,這對編過 AOSP for Android 手機的人來說應該不陌生(以 Nexus One 為例,早期需透過 extract-files.sh 取出),對於 PandaBoard 來說,請直接在 http://code.google.com/android/nexus/drivers.html#panda 下載


$ mkdir ~/bin ~/projects/android-ics
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod 755 ~/bin/repo
$ export PATH=$PATH:~/bin/repo
$ cd ~/projects/android-ics
$ repo init -u https://android.googlesource.com/platform/manifest
$ repo sync
$ source build/envsetup.sh
$ lunch full_panda-eng
$ make fastboot
$ wget https://dl.google.com/dl/android/aosp/imgtec-panda-itl41d-dfebf249.tgz  (這是 Android 4.0.1 (ITL41D) 版本)
$ tar -xvf imgtec-panda-itl41d-dfebf249.tgz
wget https://dl.google.com/dl/android/aosp/imgtec-panda-iml74k-cfb7bdad.tgz  (這是 Android 4.0.3 (IML74K) 版本)
$ tar -xvf imgtec-panda-iml74k-cfb7bdad.tgz
$ wget https://dl.google.com/dl/android/aosp/imgtec-panda-imm76i-67545da7.tgz (這是 Android 4.0.4 (IMM761) 版本,建議上官網查看最新版)
$ tar -xvf imgtec-panda-imm76i-67545da7.tgz
$ ./extract-imgtec-panda.sh
輸入 I ACCEPT
$ make -j 2 (看自己是幾核心 CPU,我在 VM 配置 2 CPU)
依硬體狀況漫長等待後,沒意外就都有編好了


設置 PandaBoard:


Init


一開始把記憶卡拔掉,接上電源及 USB 線後,可以看到靠近 SD 卡插槽有一個燈在亮(對VirtualBox來說,USB裝置可以間斷地偵測到Texas Instruments OMAP4440設備),接著透過 usbboot 指令把 bootload 餵進去後,就可以看到另一個燈閃動(對VirtualBox來說,USB裝置可以穩定偵測到Texas Instruments panda設備,請記的手動附加一下)


$ cd ~/projects/android-ics
$ sudo device/ti/panda/usbboot device/ti/panda/bootloader.bin
using built-in 2ndstage.bin
reading ASIC ID
CHIP: 4440
IDEN: 0000000000000000000000000000000000000000
MPKH: 0000000000000000000000000000000000000000000000000000000000000000
CRC0: ########
CRC1: 00000000
sending 2ndstage to target... ########
waiting for 2ndstage response...
sending image to target...


檢查是否偵測到裝置(對VirtualBox使用者,需手動附加偵測到的 Texas Instruments panda 設備):


$ sudo out/host/linux-x86/bin/fastboot devices
################        fastboot


接上 SD 卡後:


$ sudo out/host/linux-x86/bin/fastboot oem format
...
OKAY [  0.420s]
finished. total time: 0.421s
$ sudo out/host/linux-x86/bin/fastboot flash xloader device/ti/panda/xloader.bin
sending 'xloader' (23 KB)...
OKAY [  0.024s]
writing 'xloader'...
OKAY [  0.248s]
finished. total time: 0.272s
$ sudo out/host/linux-x86/bin/fastboot flash bootloader device/ti/panda/bootloader.bin
sending 'bootloader' (157 KB)...
OKAY [  0.110s]
writing 'bootloader'...
OKAY [  0.303s]
finished. total time: 0.413s
$ sudo out/host/linux-x86/bin/fastboot erase cache
erasing 'cache'...
OKAY [102.520s]
finished. total time: 102.520s
$ sudo out/host/linux-x86/bin/fastboot -p panda flash userdata
sending 'userdata' (10504 KB)...
OKAY [  6.411s]
writing 'userdata'...
OKAY [  9.763s]
finished. total time: 16.174s
$ sudo out/host/linux-x86/bin/fastboot -p panda flashall
--------------------------------------------
Bootloader Version...: U-Boot 1.1.4-g157fe435
Baseband Version.....:
Serial Number........: ################
--------------------------------------------
checking product...
OKAY [  0.006s]
sending 'boot' (3750 KB)...
OKAY [  2.331s]
writing 'boot'...
OKAY [  2.381s]
sending 'system' (146598 KB)...
OKAY [ 89.414s]
writing 'system'...
OKAY [119.862s]
rebooting...

finished. total time: 215.212s 


接著正確寫入後系統會自行重開機,大概 15 分左右就好了!記得要插好 USB 滑鼠,因為 Android 初始化沒多久就會黑屏休息,需要用滑鼠移動一下讓他清醒


各指令對應的 ttyUSB0 的輸出:


卸下 SD 卡開機:


[ aboot second-stage loader ]

MSV=00000000
jumping to 0x82000000...

U-Boot 1.1.4-g157fe435 (Oct 21 2011 - 09:46:20)

Load address: 0x80e80000
DRAM:  1024 MB
Flash:  0 kB
Using default environment

In:    serial
Out:   serial
Err:   serial

efi partition table:
Read not permitted as Card on SLOT-0 not Initialized
efi partition table not found
Net:   KS8851SNL
Panda: GPIO_113 pressed: entering fastboot....
I2C read: I/O error
Device Serial Number: ################
Fastboot entered...


$ sudo out/host/linux-x86/bin/fastboot oem format


blocks 15661056

new partition table:
     256     128K xloader
     512     256K bootloader
    2048       8M recovery
   18432       8M boot
   34816     512M system
 1083392     256M cache
 1607680     512M userdata
 2656256    2254M media


$ sudo out/host/linux-x86/bin/fastboot flash xloader device/ti/panda/xloader.bin


Starting download of 23824 bytes

downloading of 23824 bytes finished
writing to partition 'xloader'
Initializing 'xloader'
Writing 'xloader'
Writing 'xloader' DONE!


$ sudo out/host/linux-x86/bin/fastboot flash bootloader device/ti/panda/bootloader.bin


Starting download of 161236 bytes
....
downloading of 161236 bytes finished
writing to partition 'bootloader'
Initializing 'bootloader'
Writing 'bootloader'
Writing 'bootloader' DONE!


$ sudo out/host/linux-x86/bin/fastboot erase cache


Initializing 'cache'
Erasing 'cache'
Erasing 'cache' DONE!


$ sudo out/host/linux-x86/bin/fastboot -p panda flash userdata


Starting download of 10756436 bytes
...
downloading of 10756436 bytes finished
writing to partition 'userdata'
Initializing 'userdata'
fastboot: userdata is in sparse format
sparse: write to mmc slot[0] @ 1607680

sparse: out-length-0x512 MB
Writing sparsed: 'userdata' DONE!


$ sudo out/host/linux-x86/bin/fastboot -p panda flashall


Starting download of 3840000 bytes
...
downloading of 3840000 bytes finished
writing to partition 'boot'
Initializing 'boot'
Writing 'boot'
Writing 'boot' DONE!
Starting download of 150116688 bytes
...
downloading of 150116688 bytes finished
writing to partition 'system'
Initializing 'system'
fastboot: system is in sparse format
sparse: write to mmc slot[0] @ 34816


其他:


第一次使用 8GB 記憶卡,但是在 fastboot -p panda flashall 指令時,看到卡在 system.img 的寫入,網路上也看不到解法?後來甚至把 flashall 改成一步步時,仍卡在 fastboot flash system system.img 的部分,最後則是乾脆換一張 4GB 記憶卡,沒錯,這樣就沒問題了!看來玩硬體就是容易把生命浪費在其他硬體身上 XD 重點那張 8GB microSD (+轉卡) 是新卡啊,還是那種要拆包裝的耶。


2012年2月10日 星期五

Android 開發筆記 - 實機模擬 GPS 座標變化 @ Android 3.2

DDMSLocation


對 Android 模擬器來說,可以透過 DDMS 模擬 GPS 座標及其變化,然而在實體機器上卻沒看到幾篇相關文章?之所以用實體機器的主因,實在是平板模擬器跑太慢了!程式還是在實體機器跑得順多囉。


用法:


在 AndroidManifest.xml 檔案中,增加 <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" /> 設定。


新增函數:


public void setMockLocation(double latitude, double longitude)
{
    LocationManager testLM = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
   if( testLM != null )
   {
      try
      {
         String mocLocationProvider = testLM.GPS_PROVIDER;
         testLM.addTestProvider(mocLocationProvider, false, false, false, false, false, false, false, 0, 5);
         testLM.setTestProviderEnabled(mocLocationProvider, true);
         Location loc = new Location(mocLocationProvider);
         loc.setTime(System.currentTimeMillis());
         loc.setLatitude(latitude);
         loc.setLongitude(longitude);
         testLM.setTestProviderLocation(mocLocationProvider, loc);
         Toast.makeText(getApplicationContext(), "MockGPS Set", Toast.LENGTH_SHORT).show();
      }
      catch( Exception e )
      {
         Toast.makeText(getApplicationContext(), "MockGPS Failed", Toast.LENGTH_SHORT).show();
      }
  }
  else
      Toast.makeText(getApplicationContext(), "No LocationManager", Toast.LENGTH_SHORT).show();
}


之後,在程式碼中就可以透過 setMockLocation(0,0) 用來,來模擬假的 GPS 囉,最大的缺點仍是需要寫程式。


 應用:


除了在應用程式中模擬 GPS 的測試外,這東西應該有機會跟其他 GPS 定位裝置結合,例如用藍芽傳輸或 wifi 傳輸的 GPS 定位裝置,把收到的 GPS 給手機定位。


2012年2月5日 星期日

BeagleBoard-xM 教學筆記 - 安裝 Android 2.3 Gingerbread 和 Android 4.0 ICS @ Ubuntu 10.04

bbxm_ics-01 bbxm_g-03


在任何嵌入式開發板上安裝 Android 好像是近幾年的習俗慣例?!所以也就來試了一下,而今年的目標定的半年目標是在開發板上安裝 Android 和進行效能調適,再遠一點則是 Webkit 方面的事。只是當我了解更多了之後,發現 Android Porting 其實沒那麼簡單,另外,做完 porting 不代表能用。


最初想只拿 Android Open Source 跟板子相關檔案後,親自一步步調教,後來發現這些事就是國內外有名的 project 在搞得,看來一個人弄真的太累了 XD 除此之外,網路上絕大部分的 porting 筆記都只是把別人設定好的安裝方式跑一次就結束了 Orz 看到這讓我輕鬆(失落)了不少。


Android 4.0 ICS 使用 rowboat Prebuilt Images:


$ wget http://rowboat.googlecode.com/files/beagleboard-xm.tar.gz
$ md5sum beagleboard-xm.tar.gz
1255347a549530fc6653772d1711a305 beagleboard-xm.tar.gz (@2012-02-04)
$ tar -xvf beagleboard-xm.tar.gz
$ cd beagleboard-xm
$ sudo ./mkmmc-android.sh /dev/sdX


Assuming Default Locations for Prebuilt Images
All data on /dev/sdX now will be destroyed! Continue? [y/n]
y
[Unmounting all existing partitions on the device ]
umount: /dev/sdX: not mounted
umount: /dev/sdX1: not mounted
umount: /dev/sdX2: not mounted
umount: /dev/sdX3: not mounted
[Partitioning /dev/sdX...]
Disk /dev/sdb doesn't contain a valid partition table
DISK SIZE - 3951034368 bytes
CYLINDERS - 480
[Making filesystems...]
[Copying files...]
[Copying START_HERE floder to boot partition]
[Copying all clips to data partition]
[Done]


重新把 microSD 插入,修改 rootfs 權限部份,否則開機會看到 Kernel panic - not syncing: No init found. 訊息


$ sudo chmod 777 -R /media/rootfs/
$ sync
$ umount /dev/sdX*


bbxm_ics-04 bbxm_ics-03


之後安置好 beagleboard-xM 後,開機約 12 分鐘就可以看到 Android ICS 介面,可以透過滑鼠去操作,只是操作上滿卡的!還滿常出現 ANR (Application is Not Responding)。果真 porting 是一回事,能不能用又是另一回事了。


Android 2.3 Gingerbread 使用 TI Prebuilt Images:


$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_DevKit_1_0/exports/TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz
$ md5sum TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz
ff14ff771aa067dff5cc40106527d119 TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz (@2012-02-03)
$ tar -xvf TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz
$ cd TI_Android_GingerBread_2_3_DevKit_1_0/Prebuilt_Images/beagleboard-xm
$ sudo ./mkmmc-android.sh /dev/sdX


Assuming Default Locations for Prebuilt Images
All data on /dev/sdX now will be destroyed! Continue? [y/n]
y
[Unmounting all existing partitions on the device ]
umount: /dev/sdX: not mounted
umount: /dev/sdX1: not mounted
umount: /dev/sdX2: not mounted
umount: /dev/sdX3: not mounted
[Partitioning /dev/sdX...]
Disk /dev/sdb doesn't contain a valid partition table
DISK SIZE - 3951034368 bytes
CYLINDERS - 480
[Making filesystems...]
[Copying files...]
[Copying START_HERE floder to boot partition]
[Copying all clips to data partition]
[Done]


bbxm_g-01 bbxm_g-02


之後安置好 beagleboard-xM 後,開機約 23 分鐘就可以看到 Android Gingerbread 介面,但是USB滑鼠鍵盤沒被啟用,在加上我也沒觸控面板,所以測試就停在這邊。


至於手動編譯及安裝 Android 的流程,方向大概如下:



  1. get toolchain & AOSP & kernel & u-boot & x-load (有些 project 已內含不需額外再抓)

  2. build u-boot (x-boot)

  3. build x-load (MLO)

  4. build AOSP (uImage & tools)

  5. build android_rootfs (透過 AOSP 產出 /out/target/product/beagleboard 裡的 root 跟 system 來建立)

  6. build boot.scr

  7. export to microSD


以 TI-Android-GingerBread-2.3-DevKit-1.0_DeveloperGuide 為例:


取得 android source code (patched):


$ mkdir ~/rowboat-android && cd ~/rowboat-android
$ repo init -u git://gitorious.org/rowboat/manifest.git -m TI-Android-GingerBread-2.3-DevKit-1.0.xml
$ repo sync


取得 TI_Android_DevKit:


$ cd ~/
$ wget http://software-dl.ti.com/dsps/dsps_public_sw/sdo_tii/TI_Android_DevKit/TI_Android_GingerBread_2_3_DevKit_1_0/exports/TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz
$ tar -xvf TI_Android_GingerBread_2_3_DevKit_1_0.tar.gz
$ ls -R TI_Android_GingerBread_2_3_DevKit_1_0/Tools/


TI_Android_GingerBread_2_3_DevKit_1_0/Tools/:
flashing-utility.tar.gz mk-bootscr mk-mmc pinmux-utility.tar.gz signGP


TI_Android_GingerBread_2_3_DevKit_1_0/Tools/mk-bootscr:
mkbootscr mkimage README


TI_Android_GingerBread_2_3_DevKit_1_0/Tools/mk-mmc:
mkmmc-android.sh README.txt


TI_Android_GingerBread_2_3_DevKit_1_0/Tools/signGP:
signGP signGP.c


環境準備:


$ mkdir ~/rowboat-android-build
$ vim ~/.bashrc
export USE_CCACHE=1
export PATH=/home/user/rowboat-android/prebuilt/linux-x86/toolchain/arm-eabi-4.3.1/bin:/home/user/bin:$PATH


建立 x-load:


$ cd ~/rowboat-android/x-load-omap3
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omap3beagle_config
$ make CROSS_COMPILE=arm-eabi-
$ cp x-load.bin ~/rowboat-android-build/
$ cp ~/TI_Android_GingerBread_2_3_DevKit_1_0/Tools/signGP/signGP ~/rowboat-android-build/
$ cd ~/rowboat-android-build/
$ $ ./signGP ./x-load.bin
$ mv x-load.bin.ift MLO


建立 u-boot:


$ cd ~/rowboat-android/u-boot-omap3/
$ make CROSS_COMPILE=arm-eabi- distclean
$ make CROSS_COMPILE=arm-eabi- omap3_beagle_config
$ make CROSS_COMPILE=arm-eabi-
$ cp u-boot.bin ~/rowboat-android-build/
$ cp tools/mkimage ~/bin


建立 kernel:


$ cd ~/rowboat-android/kernel
$ make ARCH=arm CROSS_COMPILE=arm-eabi- distclean
$ make ARCH=arm CROSS_COMPILE=arm-eabi- omap3_beagle_android_defconfig
$ make ARCH=arm CROSS_COMPILE=arm-eabi- uImage
$ cp arch/arm/boot/uImage ~/rowboat-android-build/


建立 android rootfs:


$ cd ~/rowboat-android/
$ time make TARGET_PRODUCT=beagleboard OMAPES=5.x -j8
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=2.3
TARGET_PRODUCT=omap3evm
TARGET_BUILD_VARIANT=eng
TARGET_SIMULATOR=
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
HOST_ARCH=x86
HOST_OS=linux
HOST_BUILD_TYPE=release
BUILD_ID=GRH55
============================================
...
real 106m20.076s (依機器情況而定,此例是在 Virtualbox 中的 Ubuntu 10.04 desktop,而 Host CPU 是 AMD x4 945)
user 115m30.980s
sys 7m31.390s
$ cd out/target/product/beagleboard
$ mkdir android_rootfs
$ cp -r root/* android_rootfs
$ cp -r system android_rootfs
$ sudo ../../../../build/tools/mktarball.sh ../../../host/linux-x86/bin/fs_get_stats android_rootfs . rootfs rootfs.tar.bz2
$ cp rootfs.tar.bz2 ~/rowboat-android-build/


建立 boot.scr:


$ cp ~/TI_Android_GingerBread_2_3_DevKit_1_0/Tools/mk-bootscr/mkbootscr ~/rowboat-android-build/
$ cd ~/rowboat-android-build/
$ ./mk-bootscr


匯入 microSD:


$ cd ~/rowboat-android-build/
$ cp -r ~/TI_Android_GingerBread_2_3_DevKit_1_0/Prebuilt_Images/beagleboard-xm/Media_Clips/ ~/rowboat-android-build/
$ cp ~/TI_Android_GingerBread_2_3_DevKit_1_0/Prebuilt_Images/beagleboard-xm/mkmmc-android.sh ~/rowboat-android-build/
$ sudo ./mkmmc-android /dev/sdX MLO u-boot.bin uImage boot.scr rootfs.tar.bz2 Media_Clips
All data on /dev/sdX now will be destroyed! Continue? [y/n]
y
[Unmounting all existing partitions on the device ]
umount: /dev/sdX: not mounted
umount: /dev/sdX1: not mounted
umount: /dev/sdx2: not mounted
umount: /dev/sdX3: not mounted
[Partitioning /dev/sdX...]
Disk /dev/sdX doesn't contain a valid partition table
DISK SIZE - 3951034368 bytes
CYLINDERS - 480
[Making filesystems...]
[Copying files...]
[Copying START_HERE floder to boot partition]
[Copying all clips to data partition]
[Done]


參考資料:


http://code.google.com/p/rowboat/wiki/ICSonBeagleboard
http://processors.wiki.ti.com/index.php/TI-Android-GingerBread-2.3-DevKit-1.0_DeveloperGuide
http://androside.com/page_contents/page_android_beagleGingerPorting.html


2012年2月1日 星期三

BeagleBoard-xM 教學筆記 - 嘗試安裝 Ubuntu Natty Narwhal (11.04) @ Ubuntu 10.04

嘗試完把 microSD 恢復到原廠後,接著在 BeagleBaord xM 安裝 Ubuntu 看看。網路上可以找到不少文件和教學,但有些過程繁瑣老舊,就來試試看 Ubuntu 官網上提的步驟了:OmapNetbook。結果?可以看到系統裝起來,但缺乏 usb 跟網路的功能,不曉得是不是因為 BeagleBoard-xM Rev C 的關係?因此若真的需要把玩 Ubuntu 的,建議使用 http://elinux.org/BeagleBoardUbuntu#Demo_Image (Ubuntu 11.10) 即可,一個步驟安裝完,並且網路或 USB 鍵盤都 ok 啦。


對我而言,第一次接觸碰到幾個問題:


什麽是 OMAP?


OMAP是德州儀器(IT)提出的「 開放式多媒體應用平台」架構。在 BBxMSRM_latest.pdf 中可以查到這塊 BeagleBoard xM 板子的 CPU 是 DM3730,而 BeagleBoard 是 OMAP3530 的,然而 DM3730 是啥?在網路上找了一下,有人說這是 OMAP3630 的開發版,其中 DM 是開發版,OMAP是客戶版,但又有一說是 OMAP 是給 cell phone 的,剩下的是 AM/DM/davinci 等等,並且說在 BeagleBoard 上的 OMAP3630/DM3730 是對應到 cell phone 上的 OMAP3530 和 OMAP 3630等,總之故事好像不少,但我需要的只是確定 DM3730 也是指 OMAP 3系列的就好,有興趣的可以在 http://www.beagleboard.org/irclogs/index.php?date=2011-02-16搜尋 DM3730 關鍵字。


什麽是 Netbook?


查 wiki 的結果就是小筆電的意思,可以去看看 Ubuntu Netbook Edition - wiki 的介紹,其中有提到 Ubuntu Netbook 11.04 已經規劃納入 Desktop 版本,因此在 11.10 時,只會看到 OMAP3 preinstalled desktop image 版本,在這之前可以看到 Instruments OMAP3 preinstalled netbook image 等。


由於 DM3730 是 OMAP3630 for cell phone world 的關係,所以在此採用 OMAP3 系列。在 http://cdimage.ubuntu.com/releases/11.04/release/ 挑選 Texas Instruments OMAP3 preinstalled netbook image 和 Texas Instruments OMAP3 preinstalled headless image 來玩玩。


https://wiki.ubuntu.com/ARM/OmapNetbook 指示即可完工,在此列一些指令筆記:


$ sudo umount /media/* (或 umount /dev/sdX* )


$ sudo time sh -c 'zcat ./ubuntu-11.04-preinstalled-headless-armel+omap.img.gz | dd bs=4M of=/dev/sdc ; sync '
0+51047 records in
0+51047 records out
1675173888 bytes (1.7 GB) copied, 1961.25 s, 854 kB/s
64.90user 5.95system 32:41.61elapsed 3%CPU (0avgtext+0avgdata 5760maxresident)k
417368inputs+3271824outputs (2major+1417minor)pagefaults 0swaps

$ sudo cp ./uImage /media/PartitionBoot/
$ sudo cp ./vmlinuz-2.6.38-8-omap /media/PartitionRoot/boot


擺好 microSD 後就可以啟動 beagleboard-xM 啦,接好 RS232 to USB 後,可以透過 screen -U /dev/ttyUSB0 115200 來查看板子目前的設定狀況(建議先用 minicom 初始化後,關掉 minicom,再用 screen,可避免 screen 有時沒資料出來),通常大概 6 分鐘以內就能看到相關設定畫面,後來的流程就像安裝 Ubuntu 一樣的選單,而 microSD(此為一開始內建卡)的讀寫速度將影響啟動所耗費的時間。選好環境設定後,大概又要等 15 分鐘的安裝初始化。


只是無論是 desktop(netbook) 或 server(headless) 版,從 USB 接的鍵盤跟滑鼠都無用 XD 這樣對 desktop 版就無法進入的初始化設定啦,而 Server 版倒可以透過 /dev/ttyUSB0 來作設定,只是設定完還是無用。在網路上打滾了一會,看到的解法大概都是更新 uImage 和 boot.src,有的是取得 Angstrom Linux Demo 的檔案,有的就乾脆依照 elinux.org 的安裝方法了 BeagleBoardUbuntu#Demo_Image


所以,我就很懶的直接用 elinux.org - BeagleBoardUbuntu#Demo_Image,只需安裝相關軟體後,按一下指令就搞定了!經過測試,網路、鍵盤無痛搞定,並且因為檔案很小,只花不到 10 分鐘(上面都要30起跳)。


$ wget http://rcn-ee.net/deb/rootfs/oneiric/ubuntu-11.10-r3-minimal-armel.tar.xz
$ tar -xJf ubuntu-11.10-r3-minimal-armel.tar.xz (若不行記得安裝xz-utils)
$ cd ubuntu-11.10-r3-minimal-armel
$ sudo time ./setup_sdcard.sh --mmc /dev/sdX --uboot "beagle_xm" (/dev/sdX是 microSD 位置)
...
Are you 100% sure, on selecting [/dev/sdX] (y/n)? y
...
Downloading Device's Bootloader ...
Unmounting Partitions ...
Using fdisk to create BOOT Partition ...
Setting Boot Partition's Boot Flag ...
Creating rootfs ext4 Partition ...
Formating Boot Partition ...
Populating Boot Partition ...
Using mkimage to create uImage ...
Using mkimage to create uInitrd ...
Copying uEnv.txt based boot scripts to Boot Partition ...
Finished populating Boot Partition ...
Transfer of Base Rootfs Complete, syncing to disk ...
Finished populating rootfs Partition
-----------------------------
setup_sdcard.sh script complete
1.32user 2.52system 6:54.94elapsed 0%CPU (0avgtext+0avgdata 14848maxresident)k
2234inputs+1093280outputs (3major+55864minor)pagefaults 0swaps


收工,改天在找看看如何解決官方 Ubuntu 提供的 Image 問題。