2015年2月27日 星期五

Captcha 解碼


收到一個工作請求,是要自動解釋網頁的 Captcha 內容。之前做過一點影像分析的研究,對於 Captcha 的解碼也感到興趣,嘗試用自己的方法處理。初步已把文字位置鎖定,但是對於文字的讀取,還是未有甚麼頭緒...。

2015年2月25日 星期三

龍珠遊戲中的 LUA 內容

繼續發掘龍珠遊戲,發現在 lua 目錄內有一個 ab_script.cpk 檔。有些程式設計員會在遊戲內建 LUA 語言,方便對遊戲邏輯作出修改而不用重新組譯整個程式。以下是在 .cpk 中的部份內容:
-- アクションバンク位置戻す用スクリプト
setPhase(9);

--changeAnime(  5, 0,   0);  -- 待機ポーズ
--changeAnime(  5, 1, 100);  -- 待機ポーズ

setDisp(8, 0, 0);
setDisp(8, 1, 0);

setMoveKey( -7, 0, 0, 0);       -- キーフ


-- 死亡 (通常)
setPhase(9);

ATK_dead = 0;
ATK_end = ATK_dead+160;

-- エフェクト全消し
removeAllEffect(0);

--[[
entryEffect( 1,   1703,   0,  -1,  0,  0,  0);   --ガラス
playSe( 1, 1041);--SE
playSe( ATK_dead, 1040);--SE
playSe( 2, 1054)


-- 死亡 (最終)
setPhase(9);

ATK_dead = 0;
ATK_end = ATK_dead+160;

-- エフェクト全消し
removeAllEffect(0);

--entryEffect( 1,   1703,   0,  -1,  0,  0,  0);   --ガラス
--playSe( 1, 1041);--SE
--playSe( ATK_dead, 1040);--SE
--playSe( 2, 10


print ("[lua]exec a0003");

ATK_01 = 0;
ATK_02 = ATK_01+86;
ATK_03 = ATK_02+12;
ATK_04 = ATK_03+20;
ATK_05 = ATK_04+26;
ATK_06 = ATK_05+15;
ATK2_01 = ATK_06+5;
ATK2_02 = ATK2_01+4;
ATK2_03 = ATK2_02+4;
ATK2_04 = ATK2_03+4;
ATK2_05 = ATK2_04+5;


print ("[lua]exec a0012");

ATK_01 = 12;
ATK_02 = ATK_01+46;
ATK_03 = ATK_02+4;
ATK_end = ATK_03+20;
----------------------------------------------
--パンチの応手鵜
setEnvZoomEnable( ATK_01,1);--ズーム


fcolor_r = 245;
fcolor_g = 245;
fcolor_b = 245;

SP_ATK_0 = 6;
SP_ATK_1 = SP_ATK_0+10;
SP_ATK_2 = SP_ATK_1+63;
SP_ATK_3 = SP_ATK_2+92;
SP_ATK_4 = SP_ATK_3+45;

SE_01 = 1035; --気を貯める
SE_02 = 1036; --気が広がる
SE_03 = 1036;

2015年2月24日 星期二

把 JPG/PNG 轉換成 MP4

在近在開發的一套流動應用程式有著大量的影片需要處理。其中一項是要把影片裁剪出頭三十秒版本,並且在結尾加上一幀網址圖片。這些大量又煩鎖的工作,最適合交由電腦處理。

利用以下指令能把一張 JPG / PNG 圖片生成為一段四秒的 MP4:
$ ffmpeg -loop 1 -f image2 -i visit_us.jpg -r 30 -t 4 visit_us.mp4

至於要生成三十秒版本的影片的指令是:
ffmpeg -i video.mov -c copy -t 00:00:30.0 video_30s.mp4

若出現「Could not find tag for codec pcm_s16le in stream #1, codec not currently supported in container」時,則要稍為改動一下指令:
ffmpeg -i video.mov -strict -2 -t 00:00:30.0 video_30s.mp4

不過,轉換出來的影片有可能無法在某些 iOS 裝置上播放,那麼指令要改為:
ffmpeg -i video.mov -strict -2 -vcodec libx264 -preset slower -s 640x360 video_640x360.mp4

2015年2月23日 星期一

研究 Dragon Ball 的 CPK 檔案格式

春節期間,龍珠 iOS 版終於推出,畫面實在畫得很好,我也玩了一陣子。有點時間,於是拆開它來研究一下。解壓 .ipa 後發現內裡有很多 .cpk 檔案,想必是遊戲的圖片或設定資料。隨手拿了 1000020.cpk 來研究。


用十六進制編輯器打開檔案一看,發現檔頭為 CPK,下方還有一點英文的東西,意味著 CPK 檔案格式是沒有進行加密。


再往下看,找到了一些檔名的東西。檔名與檔名之間以 00 來分隔,明顯是字串終結的意思。同時代表這個檔案內包含著這些檔案。亦即是說 CPK 是把數個檔案打包成一個大檔案而已。


繼續向下看,見到了「.PNG」內容。「89 50 4E 47」正正是 PNG 的檔頭,即是說這就是一張 PNG 圖的開首位置。


簡單地把「89 50 4E 47」前所有數據刪除後儲存,把 .cpk 換成 .png 便能打開圖片:


如果繼續向下看,能找到更多 PNG 的檔頭呢!這些圖都是「比達」的圖,代表 1000020 是「比達」的內容吧!

2015年2月21日 星期六

Raspberry Pi 2 跟 iPhone 點對點 WiFi 連線失敗

自從購買了 NoIR 鏡頭模組之後,很想把它製作成一台平價的紅外線相機。由於定位是平價,因此不打算加入屏幕模組,希望透過 WiFi 跟 iPhone 連接,能即時查閱拍攝圖片。於是著手了關於把 WiFi USB 手指變成熱點的可能性。

網上找到相關教學,但手指要本身支援。按照教學步驟測試並不成功,應該是我的 D-Link DWA-131 不支援 Access Point 模式。後來想到利用退役了的 iPhone 製造熱點,然後讓 Raspberry Pi 2 連接。反正熱點名稱及密碼可以固定不變。可惜,舊 iPhone 在沒有 SIM 卡的情況下是無法設定成為熱點。看來要用藍牙連線代替...。

2015年2月20日 星期五

在 Raspberry Pi 2 上安裝 Nginx

在 Raspberry Pi 上些過安裝 Apache2,但對於 Pi 來說似乎過份了點,今次在 Raspberry Pi 2 上嘗試安裝 Nginx。一個較 Apache2 輕量點的網頁服務器。首先是來一個更新及升級:
sudo apt-get update && sudo apt-get upgrade
完成後安裝 Nginx:
sudo apt-get install nginx
啟動 Nginx:
sudo /etc/init.d/nginx start

2015年2月19日 星期四

Tomcat + Java + MySQL

由於 AS2 需要運行在 Tomcat 之上,而沿用開的 Apache + PHP + MySQL 方式跟 Tomcat 沒有接軌。我的解決方法是讓 Java 能直接讀取 MySQL 內的數據。因此需要設定 Tomcat + Java + MySQL 的方案。安裝方法如下:

1. 到 http://dev.mysql.com/downloads/connector/j/ 下載 MySQL Connector/J
2. 解壓後把 mysql-connector-java-5.1.34-bin.jar 拷到 /usr/local/tomcat7/lib
3. 修改 /usr/local/tomcat7/conf/context.xml
4. 在 <context></context> 中加入「<Resource name="jdbc/MySQLDS" auth="Container" type="javax.sql.DataSource" factory="org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" username="username" password="password" maxActive="20" maxIdle="10" maxWait="-1" />」
5. 以 /usr/local/tomcat/bin/shutdown.sh 關閉 Tomcat
6. 以 /usr/local/tomcat/bin/startup.sh 重啟 Tomcat
7. 完成

留意上面用了 localhost:3306,所以記得要在 /etc/sysconfig/iptables 開通 3306 埠:
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m comment --comment "SFTP port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -m comment --comment "Apache WEB server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -s 192.168.1.1 -m comment --comment "MySQL server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -m comment --comment "Tomcat Server port" -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
之後簡單寫了一個 JSP 測試以 JNDI 方式讀取 MySQL 內的數據:
<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@page import="com.mysql.*,java.util.*,javax.naming.*,java.sql.*,javax.sql.*" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Insert title here</title>
 </head>
 <body>
  <h1>JSP MySQL Connection Test</h1>
<%
 Context context = new InitialContext();
 String lookup = "java:comp/env/jdbc/MySQLDS";
 DataSource dataSource = (DataSource)context.lookup(lookup);
 Connection connection = dataSource.getConnection();
 Statement statement = connection.createStatement();

 String query = "SELECT * FROM databaseName.tableName";
 ResultSet resultSet = statement.executeQuery(query);
 while (resultSet.next())  {

   out.println(resultSet.getString(1));
   out.println(resultSet.getString(2));
   out.println("<br>");
 }
%>
 </body>
</html>

2015年2月18日 星期三

安裝 AS2 Connector

1. 到 rssbus 下載 AS2 Connector
2. 解壓 AS2ConnectorCrossPlatformUnixLinuxJavaSetup.zip
3. 把 RSSBusApps 內的 rssbus.war 改名為 rssbus.zip 並解壓
4. 把 rssbus 目錄拷到 /usr/local/tomcat7/webapps/rssbus
5. 在瀏覽器打開「http://yourserver:8080/rssbus/」
6. 用之前在 Tomcat 設定好的帳戶登入
7. 點擊 Free Single Partner License 下的「INSTALL LICENSE」


8. 成功後「Maximum Partners」會變成「1」


9. 點擊「Profile」並填妥有關資料後儲存

2015年2月17日 星期二

安裝 Tomcat 7

最近一個項目需要用上 Applicability Statement 2 (AS2),對於不熟悉服務器端的我實在感到力不從心。經過一步一步的學習,找到了 FreeAS2。它需要安裝在 Tomcat 之上,因此要先行在服務器安裝 Tomcat。安裝步驟如下:
# cd /tmp
# wget http://apache.communilink.net/tomcat/tomcat-7/v7.0.59/bin/apache-tomcat-7.0.59.tar.gz
# tar xzf apache-tomcat-7.0.59.tar.gz
# mv apache-tomcat-7.0.59 /usr/local/tomcat7
啟動 Tomcat:
# /usr/local/tomcat7/bin/startup.sh
關閉 Tomcat:
# /usr/local/tomcat7/bin/shutdown.sh
由於 Tomcat 用上 8080 埠,因此記得要在 /etc/sysconfig/iptables 進行開通:
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m comment --comment "SFTP port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -m comment --comment "Apache WEB server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -s 192.168.1.1 -m comment --comment "MySQL server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -m comment --comment "Tomcat Server port" -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
最後,還要設定用戶權限。
# vi /usr/local/tomcat7/conf/tomcat-users.xml
<role rolename="programmers" />
<user username="username" password="password" roles="rssbus_appuser,rssbus_admin,admin-gui,manager-gui,manager-status,manager-script,manager-jmx,programmers" />

2015年2月16日 星期一

開通 3306 埠

隨著不斷的發展,公司的內部服務器由一個變成兩個再變成三個。而項目的數量亦不斷增加,同一個客戶的不同項目變成儲存在不同的服務器。要提取數據也變得複雜起來,有時要到服務器甲、有時則要到服務器乙。為了解決以上問題,我們需要一個提取數據的中心,它會到不同的服務器提取數據,客人只需要到這個中心就能取得不同項目的數據。其中一樣要做的事情就是開通一個埠作為存取資料庫數據。
vi /etc/sysconfig/iptables
把內容改為:
# Generated by iptables-save v1.4.7 on Mon Feb 16 13:15:58 2015
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -m comment --comment "SFTP port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -m comment --comment "Apache WEB server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 3306 -s 192.168.1.10 -m comment --comment "MySQL server port" -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 8080 -m comment --comment "Tomcat Server port" -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
# Completed on Mon Feb 16 13:15:58 2015
留意所有 ACCEPT 的句子都要放在 REJECT 之上。不然句子會變成無效。

2015年2月15日 星期日

NoIR 鏡頭測試


NoIR 鏡頭附帶了一片深藍色的膠片,沒有說明過是甚麼用途。今早女兒們要上主日學,難得有時間研究一下。圖的左面是在鏡頭前加上深藍色膠片的效果;右面是沒有加時的效果。看來深藍色膠片就像遙控器的深紅色濾鏡一樣,用來阻擋非紅外線穿過。看看本來帶紫紅色的蘭花都變成白色了。

2015年2月12日 星期四

把 Raspberry Pi 2 顯示旋轉 90 度 (Ubuntu)

換上安裝了 Ubuntu 的 Raspberry Pi 2 後,由於設定不同,很多東西都要重新學習。其中一樣是旋轉螢幕。

原本在 Raspberry Pi (Raspbian) 中 /boot/config.txt 內的 display_rotate 選項改為 3 便會把顯示內容旋轉 270 度;但在 Ubuntu 下 /boot 是有的但 config.txt 則是放在 /boot/uboot。然而只要同樣加入 display_rotate=3 便行。

每個 Linux 版本都有不同的路徑與設置,這也是我當初學習時遇到困難的地方...。

2015年2月11日 星期三

設定 Raspberry Pi 連接隱藏網絡

Connect Raspberry Pi to Hidden Wireless Network

我的 Raspberry Pi 設定得差不多了,開始要由實線改為無線連線。不過,Raspberry Pi 要連接隱藏網絡,需要作比實線更多的設定。首先準備好加密了的登入密碼,在終端機輸入:
wpa_passphrase "Your Hidden SSID" 'Your Wireless LAN Password'
這樣就會看差不多的輸出:
network={
 ssid="Your Hidden SSID"
 #psk="Your Wireless LAN Password"
 psk= 8888ce8df8f88cdb888cdeceebcd88da8de8888888f88aa888cbd8888888888b
}
之後便是檢查無線隱藏網絡的資料。在終端機輸入:
sudo iwlist wlan0 scan
會看到差不多的輸出:
Cell 04 - Address: 66:33:44:88:00:22
                    ESSID:""
                    Protocol:IEEE 802.11bgn
                    Mode:Master
                    Frequency:2.462 GHz (Channel 11)
                    Encryption key:on
                    Bit Rates:1 Mb/s; 2 Mb/s; 5.5 Mb/s; 11 Mb/s; 6 Mb/s
                              9 Mb/s; 12 Mb/s; 18 Mb/s; 24 Mb/s; 36 Mb/s
                              48 Mb/s; 54 Mb/s
                    Extra:rsn_ie=00000000000abc000000000abc000000000abc000000
                    IE: IEEE 802.11i/WPA2 Version 1
                        Group Cipher : CCMP
                        Pairwise Ciphers (1) : CCMP
                        Authentication Suites (1) : PSK
                    Signal level=100/100
要留意的是,有些無線網絡的 Group Cipher 及 Pairwise Ciphers 是 TKIP。而我的 Apple AirPort 則是 CCMP。如果是 TKIP 的話,則把以下「CCMP」的字眼改為「TKIP」即可。現在正式進入設定的工作。在終端機輸入:
sudo nano /etc/network/interfaces
然後把有關 wlan0 的內容改為:
auto wlan0
allow-hotplug wlan0

iface wlan0 inet dhcp
wpa-scan-ssid 1
wpa-ap-scan 1
wpa-key-mgmt WPA-PSK
wpa-proto RSN WPA
wpa-pairwise CCMP TKIP
wpa-group CCMP TKIP
wpa-ssid "Your Hidden SSID"
wpa-psk 8888ce8df8f88cdb888cdeceebcd88da8de8888888f88aa888cbd8888888888b
儲存後重新啟動 Raspberry Pi 就能成功連線。

2015年2月10日 星期二

Raspberry Pi 2 到着


上周二訂購的 Raspberry Pi 2 經過五個工作天後終於送到。今次我嘗試安裝只支援 Pi 2 的 Snappy Ubuntu Core。安裝方法如下:
MacBook-Pro-Pacess:~ pacess$ sudo diskutil unmount /dev/disk2s1
Password:
Volume RPI_2 on disk2s1 unmounted
MacBook-Pro-Pacess:~ pacess$ cd Desktop/
MacBook-Pro-Pacess:Desktop pacess$ sudo dd bs=1m if=pi-snappy.img of=/dev/rdisk2
2861+1 records in
2861+1 records out
3000000000 bytes transferred in 296.119930 secs (10131030 bytes/sec)
MacBook-Pro-Pacess:Desktop pacess$
把 Micro SD 卡插入 Raspberry Pi 2 後開機,同樣出現一張 RGB 色階圖,今次等候的時間比以往長。出現四顆 Raspberry 圖案一會兒會後便跳到登入的終端,沒有了過去一大堆的載入資料。以 ubuntu 名稱及 ubuntu 密碼登入。輸入 rasp-config 卻出現錯誤。
ubuntu@localhost:~$ sudo rasp-config
sudo: rasp-config: command not found
ubuntu@localhost:~$ 
看看 Apache2 是否已被執行:
ubuntu@localhost:/$ service apache2 status
● apache2.service
   Loaded: not-found (Reason: No such file or directory)
   Active: inactive (dead)
ubuntu@localhost:/$ 
似乎跟我認識的環境很不一樣。用 MacBook 以 SSH 登入呢,反而成功了。LAN 沒有設定便能正常運作,這點較易入手。由於 apt-get 沒有了,改為 snappy,嘗試更新一下系統:
ubuntu@localhost:/etc$ snappy update-versions
Traceback (most recent call last):
  File "/usr/bin/snappy", line 25, in 
    status = Main().__main__()
  File "/usr/lib/python3/dist-packages/snappy/main.py", line 195, in __main__
    return callback(args)
  File "/usr/lib/python3/dist-packages/snappy/main.py", line 292, in _do_update_versions
    ClickDataSource().versions()):
  File "/usr/lib/python3/dist-packages/snappy/click.py", line 189, in versions
    all_updates_list = repo.get_upgradable()
  File "/usr/lib/python3/dist-packages/click/repository.py", line 183, in get_upgradable
    headers={"content-type": "application/json"})
  File "/usr/lib/python3/dist-packages/click/network.py", line 70, in http_request
    curl.perform()
pycurl.error: (60, 'server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none')
ubuntu@localhost:/etc$ 
那麼看看目前版本呢?
ubuntu@localhost:/etc$ snappy versions
Traceback (most recent call last):
  File "/usr/bin/snappy", line 25, in 
    status = Main().__main__()
  File "/usr/lib/python3/dist-packages/snappy/main.py", line 195, in __main__
    return callback(args)
  File "/usr/lib/python3/dist-packages/snappy/main.py", line 334, in _do_versions
    click_versions = ClickDataSource().versions(all)
  File "/usr/lib/python3/dist-packages/snappy/click.py", line 189, in versions
    all_updates_list = repo.get_upgradable()
  File "/usr/lib/python3/dist-packages/click/repository.py", line 183, in get_upgradable
    headers={"content-type": "application/json"})
  File "/usr/lib/python3/dist-packages/click/network.py", line 70, in http_request
    curl.perform()
pycurl.error: (60, 'server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none')
ubuntu@localhost:/etc$ 
又死...。看來 Ubuntu 還沒有準備好...。

2015-02-12 更新
得到網友 Char 的提點,以下方法能解決上述錯誤:
ubuntu@localhost:~$ sudo date -s "Thu Feb 12 22:06:00 UTC 2015"
Thu Feb 12 22:06:00 UTC 2015
ubuntu@localhost:~$ sudo snappy versions
Part         Tag   Installed  Available  Fingerprint     Active  
ubuntu-core  edge  2          -          f442b1d8d6db3f  *       
webdm        edge  0.1        -          1604c8b7c9f6c5  *       
ubuntu@localhost:~$ 

2015年2月9日 星期一

Raspberry Pi 的遙控拍攝程式


Raspberry Pi 的 NoIR 鏡頭無法成功安裝,我懷疑跟 OctoPrint 有關。於是我選擇了 NOOBS 影像把 SD 卡重新安裝。完成後在終端輸入「raspistill -v」,成功看到鏡頭拍下的畫面。

為了進一步學習 Raspberry Pi 及 Python 的使用方法,我想到以 Raspberry Pi 作為網頁服務器,利用指定網頁的按鈕控制鏡頭進行相片拍攝,並把影像傳回瀏覽器。花了一個半小時編程,成功造出了以上項目。代碼非常簡單。首先要製作 capture.py。它是一個 Python 程式,負責拍攝工作,並在完成後以 JSON 格式傳回相片路徑給瀏覽器:
#!/usr/bin/env python

##------------------------------------------------------------------------------
## Raspberry Pi Remote Camera
## Written by Pacess HO
## Copyright (c) 2015 Pacess Studio.  All rights reserved.
##------------------------------------------------------------------------------

import picamera
import datetime
import time
import json

##==============================================================================
with picamera.PiCamera() as camera:

 #  Prepare output filename
 today = time.time()
 dateObject = datetime.datetime.fromtimestamp(today)
 filename = dateObject.strftime("%Y%m%d%H%M%S")+".jpg"
 filePath = "./_files/"+filename

 #  Prepare camera
 camera.resolution = (640, 480)
 camera.rotation = 180
 camera.start_preview()

 #  Camera warm-up time
 time.sleep(2)

 camera.capture(filePath)

 #  Prepare for output
 response = {"apiName": "capture.py", "authToken": "", "message": "Done", "result":filePath, "status":0}

 print("Content-type: application/json")
 print("")
 print(json.JSONEncoder().encode(response))
完成後先在瀏覽器試一試 http://raspberrypi.local/capture.py。竟然是顯示了 capture.py 內整份內容。要令 Apache2 執行 Python 程式,需要做一點設定。在終端機執行:
sudo nano /etc/apache2/sites-enabled/000-default
把 <Directory /var/www/> 內加入以下紅字:
<Directory /var/www/>
  Options Indexes FollowSymLinks MultiViews +ExecCGI
  AllowOverride None
  Order allow,deny
  allow from all
  AddHandler cgi-script .py
</Directory>
儲存後回到終端機執行:
sudo service apache2 reload
回到瀏覽器再試。今次出現了「Internal Server Error」:

查看 Apache2 的錯誤訊息:
tail -f /var/log/apache2/error.log
發現問題是:
malformed header from script. Bad header=* failed to open vchiq instanc: capture.py
解決方法是執行:
sudo chmod o+rwx /dev/vchiq
再次回到瀏覽器,今次成功了!有了拍攝的程式,下一步便是網頁介面。我利用了上星期學到的 Bootstrap 來開發:
<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=utf8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <meta name="apple-mobile-web-app-status-bar-style" content="black" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="format-detection" content="telephone=no" />

  <title>Raspberry Pi Remote Camera</title>

  <!--  Bootstrap core CSS  -->
  <link rel="stylesheet" href="/_css/bootstrap.min.css">

  <!--  Custom styles for this page  -->
  <link rel="stylesheet" href="/_css/main.css" type="text/css">

  <!--  Custom font  -->
  <link href='http://fonts.googleapis.com/css?family=Cookie' rel='stylesheet' type='text/css'>

  <!--  Custom styles for this template  -->
  <link rel="stylesheet" href="/_css/main.css" type="text/css">

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
  <script src="/_js/main.js"></script>
 </head>

 <body>
  <div id="divTitle">Raspberry Pi Remote Camera</div>
  <div class="divBody">
   <img src="/_files/capture.jpg" id="previewImage" /><br>
   <button type="button" class="btn btn-warning" onclick="capture();">Capture</button>
  </div>


  <!--  Bootstrap core JavaScript
  ==================================================  -->
  <!--  Placed at the end of the document so the pages load faster  -->
  <script src="/_js/bootstrap.min.js"></script>
 </body>
</html>
接著便是網頁的 Javascript 程式,負責跟 Raspberry Pi 的 capture.py 溝通:
//------------------------------------------------------------------------------
// Raspberry Pi Remote Camera
// Written by Pacess HO
// Copyright (c) 2015 Pacess Studio.  All rights reserved.
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
// App constants
//------------------------------------------------------------------------------
var API = {
 CAPTURE: "capture.py",
};

var REQUEST_METHOD = {
 GET: "GET",
 POST: "POST"
};

var HTTP_STATUS = {
 OK: 0,

 ERROR_INVALID_AUTH_TOKEN: 10,

 ERROR_PARAMETER_NOT_FOUND: 20,

 ERROR_USER_ALREADY_EXISTS: 30,
 ERROR_USER_UPDATE_FAILED: 31,

 ERROR_ACCESS_DENIED: 80,

 ERROR_UNKNOWN: 99,

 //  HTTP Status
 ERROR_FILE_NOT_FOUND: 404,
 ERROR_INTERNAL_SERVER_ERROR: 500
};

//------------------------------------------------------------------------------
//  AJAX related functions
//------------------------------------------------------------------------------
function makeParameterString(parameters)  {
 var keyValues = [];

 for (var key in parameters)  {
  if (parameters.hasOwnProperty(key))  {
   keyValues.push(key + "=" + parameters[key]);
  }
 }
 
 return keyValues.join("&");
}

//------------------------------------------------------------------------------
function ajaxPost(urlString, parameterString, callback)  {
 ajaxRequest(urlString, REQUEST_METHOD.POST, parameterString, callback)
}

//------------------------------------------------------------------------------
function ajaxRequest(urlString, requestMethod, parameterString, callback)  {

 // Create new request 
 var request = new XMLHttpRequest();
 request.onreadystatechange = function()  {
  if (request.readyState == 4)  {

   switch (request.status)  {
    default:  {
     var httpStatus = request.status;
     var jsonObject = {
      timeStamp: 0,
      apiName: urlString.split("/").pop(),
      status: httpStatus,
      authToken: 0,
      results: "發生錯誤,請稍後再試 ("+exitValue+")"
     };

     var jsonString = JSON.stringify(jsonObject);
     callback(jsonString);
    }  break;

    case 200:  {
     callback(request.responseText);
    }  break;
   }
  }
 };
 
 // Send the request now
 if (requestMethod == REQUEST_METHOD.GET)  {

  urlString = urlString + "?" + parameterString;
  request.open(requestMethod, urlString, true);
  request.send();
  return;
 }

 if (requestMethod == REQUEST_METHOD.POST)  {
  request.open(requestMethod, urlString, true);
  request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  request.send(parameterString);
  return;
 }
}

//------------------------------------------------------------------------------
function connectionResponse(data)  {
 var jsonObject = data;
 if (data == undefined)  {alert("Undefined");  return;}
 if (data == "")  {alert("Empty");  return;}
 if (typeof(data) == "string")  {jsonObject = jQuery.parseJSON(data);}

 // Fetch value from the JSON object
 _authToken = jsonObject.authToken;
 var apiName = jsonObject.apiName;
 var message = jsonObject.message;
 var result = jsonObject.result;
 var status = jsonObject.status;
 switch (status)  {

  //  404 = File not found
  case HTTP_STATUS.ERROR_FILE_NOT_FOUND:  alert(message);  break;

  //  500 = Internal server error
  case HTTP_STATUS.ERROR_INTERNAL_SERVER_ERROR:  alert(message);  break;

  case HTTP_STATUS.ERROR_INVALID_AUTH_TOKEN:
  case HTTP_STATUS.ERROR_PARAMETER_NOT_FOUND:
  case HTTP_STATUS.ERROR_ACCESS_DENIED:
  case HTTP_STATUS.ERROR_UNKNOWN:  {
   alert(message);
  }  return;
 }
 
 //------------------------------------------------------------------------------
 switch (apiName)  {
  case API.CAPTURE:  {
   var filePath = result;
   $("#previewImage").attr("src", filePath);
  }  break;
 }
}

//------------------------------------------------------------------------------
//  Custom functions
//------------------------------------------------------------------------------
function capture()  {

 // Connect now
 var apiPath = API.CAPTURE;
 ajaxPost(apiPath, null, connectionResponse);
}
這時,所有程式已經準備好,執行看看。成功能令 Raspberry Pi 進行指攝並傳回最新圖像!

2015年2月8日 星期日

在 Raspberry Pi 安裝 Apache

pi@raspberrypi ~ $ sudo apt-get install apache2 php5 libapache2-mod-php5
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  apache2-mpm-prefork apache2-utils apache2.2-bin apache2.2-common libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libonig2 libqdbm14 lsof php5-cli php5-common ssl-cert
Suggested packages:
  apache2-doc apache2-suexec apache2-suexec-custom php-pear openssl-blacklist
The following NEW packages will be installed:
  apache2 apache2-mpm-prefork apache2-utils apache2.2-bin apache2.2-common libapache2-mod-php5 libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libonig2 libqdbm14 lsof php5 php5-cli
  php5-common ssl-cert
0 upgraded, 17 newly installed, 0 to remove and 0 not upgraded.
Need to get 7493 kB of archives.
After this operation, 22.4 MB of additional disk space will be used.
Do you want to continue [Y/n]? Y
為了學習更多 Raspberry Pi 及 Python 的東西,我想出了一個項目。這個項目需要用上 Apache。而 NOOBS 預設沒有帶 Apache,需要另外安裝。在終端輸入「sudo apt-get install apache2 php5 libapache2-mod-php5」。等候安裝完成輸入「sudo reboot」重啟一次。最後還要把網頁的根目錄設定權限「sudo chown -R pi /var/www」。大功告成!

2015年2月7日 星期六

在 Raspberry Pi 安裝 Bonjour

Installing Bonjour Support on Raspberry Pi

以往安裝的 Raspberry Pi 都輸入了固定 IP 地址,在家使用沒有問題;但拿到別的地方用時,由於網域不同,造成無法連線。最好的方法是利用 DHCP 分派 IP 地址。但這又衍生另一問題,怎樣知道 Raspberry Pi 的地址呢?尤其是沒有接上顯示屏的情況。要解決以上問題,只要安裝 Bonjour 就可以。安裝方法如下:

1. sudo apt-get update
2. sudo apt-get upgrade
3. sudo apt-get install avahi-daemon

成功安裝後,便能以「ssh pi@raspberrypi.local」來遙距登入 Raspberry Pi。若是不喜歡「raspberrypi.local」這個名稱,可以在 SSH 登入之後輸入「sudo nano /etc/hosts」。把 127.0.1.1 那行後方的「raspberrypi」改成想要的名字;儲存後輸入「sudo nano /etc/hostname」,同樣把「raspberrypi」改成想要的名字並儲存。最後輸入「sudo /etc/init.d/hostname.sh」把以上改動生效,再以「sudo reboot」重新啟動便能完成。

2015年2月6日 星期五

解決 Warning: strtotime() 問題


昨天有朋友介紹我用 CakePHP。我不認識,於是了解一下。下載 CakePHP 後安裝到網頁服務器,發現兩個問題「Warning: strtotime(): It is not safe to rely on the system's timezone settings.」及「Warning: _cake_core_ cache was unable to write 'cake_dev_en-us' to File cache in xxx」。

第一個的解決方法是修改 CentOS 下 /etc/php.ini 內的 Date.Timezone 值為 "Asia/Hong_Kong"。重啟 Apache 便能解決。

第二個是因為 CakePHP 的 app/tmp 未設定為寫入權限,只要改為 777 就可以。

2015年2月5日 星期四

mmal: mmal_vc_component_enable: failed to enable component: ENOSPC


昨晚在網上訂購了 Raspberry Pi 的 NoIR 鏡頭,今日下午便送到公司。實在很有效率。回家安頓好女兒後拿出來試試,可惜出現「mmal: mmal_vc_component_enable: failed to enable component: ENOSPC」錯誤。搞了一陣子也未能解決...。
pi@octopi ~ $ raspistill -v 

raspistill Camera App v1.3.4

Width 2592, Height 1944, quality 85, filename (null)
Time delay 5000, Raw no
Thumbnail enabled Yes, width 64, height 48, quality 35
Link to latest frame enabled  no
Full resolution preview No
Capture method : Single capture

Preview Yes, Full screen Yes
Preview window 0,0,1024,768
Opacity 255
Sharpness 0, Contrast 0, Brightness 50
Saturation 0, ISO 0, Video Stabilisation No, Exposure compensation 0
Exposure Mode 'auto', AWB Mode 'auto', Image Effect 'none'
Metering Mode 'average', Colour Effect Enabled No with U = 128, V = 128
Rotation 0, hflip No, vflip No
ROI x 0.000000, y 0.000000, w 1.000000 h 1.000000
mmal: mmal_vc_component_enable: failed to enable component: ENOSPC
mmal: camera component couldn't be enabled
mmal: main: Failed to create camera component
mmal: Failed to run camera app. Please check for firmware updates

在網上找到幾個聲稱有效的解決方案,試過還是不行:
1. sudo apt-get update
2. sudo apt-get upgrade
3. sudo rpi-update
4. sudo rpi-update 8234d5148aded657760e9ecd622f324d140ae89
5. sudo apt-get dist-upgrade

2015年2月3日 星期二

馬雲與青年有約:從夢想到成功創業



昨晚舉行的「馬雲與青年有約:從夢想到成功創業」,有不少朋友進場,也有朋友在席跟馬雲對話。我對馬雲的認識不深,但作為一個能白手興家的人,一定有值得學習的地方。看畢全片,有很多值得深思及啟發的地方,在創業十字路口上的我得到了啟示。

  • 最令我有感觸的是「創業是要改變自己」。曾經聽過。很難;也很容易
  • 成功是要靠團隊,這點無容置疑,單憑一己之力必定做死
  • 團隊能集合擅長不同領域的人,能較有成功的機會
  • 創業要相信自己,堅定不移
  • 創業的人要融入市場、融入團隊,才能認識箇中變化與機會
  • 機會是平等的