2015年4月30日 星期四

重寫工具程式


家中的 CentOS 重啟後發生問題,導致無法正常開機。唯有重新安裝。及後發現原本放在 CentOS 的 PHP 及 Javascript 程式沒有備份,實在失策。碰巧希望使用之前開發了的工具,發現已經沒有了,需要重新編寫。有見及此,索性把早兩三個月學到的 Bootstrap 用在新版本內。增加美感的同時,順道改良了流程。把 PHP 的迴圈改為 AJAX+PHP 迴圈,把訊息傳回瀏覽器,方便了解運作情況。

2015年4月29日 星期三

PHP:取得股票過去記錄

昨天提到的方法是獲取當刻的股票報價,對於收集過往績效沒有幫助。這樣要使用另一個查詢接口:
<?php

$stock = "2628.HK"
$startDate = "2015-04-27";
$endDate = $startDate;

$api = "https://query.yahooapis.com/v1/public/yql?q=".
       urlencode("select * from yahoo.finance.historicaldata").
       urlencode(" where symbol in ('$stock') AND startDate='$startDate' AND endDate='$endDate'").
       "&format=json&diagnostics=true".
       "&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=";

//-----------------------------------------------------------------------------
try  {
    $rawData = file_get_contents($api);
    $jsonData = json_decode($rawData);

    $price = $jsonData->query->results->quote->Close;
    echo("Price: ".$price);

}  catch (Exception $e)  {
    echo($e->getMessage();)
}

?>

以下是傳回的結果:
{
    "query":{
        "count":1,
        "created":"2015-04-29T13:20:58Z",
        "lang":"en-US",
        "diagnostics":{
            "url":[{
                "execution-start-time":"0",
                "execution-stop-time":"1",
                "execution-time":"1",
                "content":"http://www.datatables.org/yahoo/finance/yahoo.finance.historicaldata.xml"
            },
            {
                "execution-start-time":"7",
                "execution-stop-time":"20",
                "execution-time":"13",
                "content":"http://ichart.finance.yahoo.com/table.csv?a=3&b=27&e=27&g=d&c=2015&d=3&f=2015&s=2628.HK"
            },
            {
                "execution-start-time":"23",
                "execution-stop-time":"34",
                "execution-time":"11",
                "content":"http://ichart.finance.yahoo.com/table.csv?a=3&b=27&e=27&g=d&c=2015&d=3&f=2015&s=2628.HK"
            }],
            "publiclyCallable":"true",
            "cache":[{
                "execution-start-time":"6",
                "execution-stop-time":"6",
                "execution-time":"0",
                "method":"GET",
                "type":"MEMCACHED",
                "content":"bc71a240dc64a8699677be3a0ddf89f6"
            },
            {
                "execution-start-time":"22",
                "execution-stop-time":"22",
                "execution-time":"0",
                "method":"GET",
                "type":"MEMCACHED",
                "content":"acdc182ddaac86c188221ed1cc78e37b"
            }],
            "query":[{
                "execution-start-time":"6",
                "execution-stop-time":"22",
                "execution-time":"16",
                "params":"{url=[http://ichart.finance.yahoo.com/table.csv?a=3&b=27&e=27&g=d&c=2015&d=3&f=2015&s=2628.HK]}",
                "content":"select * from csv(0,1) where url=@url"
            },
            {
                "execution-start-time":"23",
                "execution-stop-time":"34",
                "execution-time":"11",
                "params":"{columnsNames=[Date, Open, High, Low, Close, Volume, Adj_Close], url=[http://ichart.finance.yahoo.com/table.csv?a=3&b=27&e=27&g=d&c=2015&d=3&f=2015&s=2628.HK]}",
                "content":"select * from csv(2,0) where url=@url and columns=@columnsNames
            }],
            "javascript":{
                "execution-start-time":"5",
                "execution-stop-time":"35",
                "execution-time":"30",
                "instructions-used":"32648",
                "table-name":"yahoo.finance.historicaldata"
            },
            "user-time":"35",
            "service-time":"25",
            "build-version":"0.2.100"
        },
        "results":{
            "quote":{
                "Symbol":"2628.HK",
                "Date":"2015-04-27",
                "Open":"37.95",
                "High":"38.30",
                "Low":"37.50",
                "Close":"38.20",
                "Volume":"45063300",
                "Adj_Close":"38.20"
            }
        }
    }
}

2015年4月28日 星期二

PHP:取得股票報價

一直都想嘗試用機器學習去建議股票買入賣出。在嘗試之前,需要拿點數據測試。但要如何取得?答案是 Yahoo API。以下簡單的 PHP 程式能獲取當天的股票報價:
<?php

$stock = "2628.HK";
$api = "https://query.yahooapis.com/v1/public/yql?q=".
       urlencode("select * from yahoo.finance.quotes").
       urlencode(" where symbol in ('$stock')").
       "&format=json&diagnostics=true".
       "&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys&callback=";

//-----------------------------------------------------------------------------
try  {
    $rawData = file_get_contents($api);
    $jsonData = json_decode($rawData);

    $quote = $jsonData->query->results->quote;
    echo("Price: ".$quote->LastTradePriceOnly);

}  catch (Exception $e)  {
    echo($e->getMessage());
}

?>

傳回 JSON 結果:
{
    "query":{
        "count":1,
        "created":"2015-04-28T13:39:43Z",
        "lang":"en-US",
        "diagnostics":{
            "url":[{
                "execution-start-time":"0",
                "execution-stop-time":"74",
                "execution-time":"74",
                "content":"http://www.datatables.org/yahoo/finance/yahoo.finance.quotes.xml"
            },
            {
                "execution-start-time":"80",
                "execution-stop-time":"159",
                "execution-time":"79",
                "content":"http://download.finance.yahoo.com/d/quotes.csv?f=aa2bb2b3b4cc1c3c4c6c8dd1d2ee1e7e8e9ghjkg1g3g4g5g6ii5j1j3j4j5j6k1k2k4k5ll1l2l3mm2m3m4m5m6m7m8nn4opp1p2p5p6qrr1r2r5r6r7ss1s7t1t7t8vv1v7ww1w4xy&s=2628.HK"
            }],
            "publiclyCallable":"true",
            "cache":{
                "execution-start-time":"79",
                "execution-stop-time":"80",
                "execution-time":"1",
                "method":"GET",
                "type":"MEMCACHED",
                "content":"5d1e1de680846a307c9874dc3d6878dc"
            },
            "query":{
                "execution-start-time":"80",
                "execution-stop-time":"159",
                "execution-time":"79",
                "params":"{
                    url=[http://download.finance.yahoo.com/d/quotes.csv?f=aa2bb2b3b4cc1c3c4c6c8dd1d2ee1e7e8e9ghjkg1g3g4g5g6ii5j1j3j4j5j6k1k2k4k5ll1l2l3mm2m3m4m5m6m7m8nn4opp1p2p5p6qrr1r2r5r6r7ss1s7t1t7t8vv1v7ww1w4xy&s=2628.HK]
                }",
                "content":"select * from csv where url=@url and columns='Ask,AverageDailyVolume,Bid,AskRealtime,BidRealtime,BookValue,Change&PercentChange,Change,Commission,Currency,ChangeRealtime,AfterHoursChangeRealtime,DividendShare,LastTradeDate,TradeDate,EarningsShare,ErrorIndicationreturnedforsymbolchangedinvalid,EPSEstimateCurrentYear,EPSEstimateNextYear,EPSEstimateNextQuarter,DaysLow,DaysHigh,YearLow,YearHigh,HoldingsGainPercent,AnnualizedGain,HoldingsGain,HoldingsGainPercentRealtime,HoldingsGainRealtime,MoreInfo,OrderBookRealtime,MarketCapitalization,MarketCapRealtime,EBITDA,ChangeFromYearLow,PercentChangeFromYearLow,LastTradeRealtimeWithTime,ChangePercentRealtime,ChangeFromYearHigh,PercebtChangeFromYearHigh,LastTradeWithTime,LastTradePriceOnly,HighLimit,LowLimit,DaysRange,DaysRangeRealtime,FiftydayMovingAverage,TwoHundreddayMovingAverage,ChangeFromTwoHundreddayMovingAverage,PercentChangeFromTwoHundreddayMovingAverage,ChangeFromFiftydayMovingAverage,PercentChangeFromFiftydayMovingAverage,Name,Notes,Open,PreviousClose,PricePaid,ChangeinPercent,PriceSales,PriceBook,ExDividendDate,PERatio,DividendPayDate,PERatioRealtime,PEGRatio,PriceEPSEstimateCurrentYear,PriceEPSEstimateNextYear,Symbol,SharesOwned,ShortRatio,LastTradeTime,TickerTrend,OneyrTargetPrice,Volume,HoldingsValue,HoldingsValueRealtime,YearRange,DaysValueChange,DaysValueChangeRealtime,StockExchange,DividendYield'"
            },
            "javascript":{
                "execution-start-time":"78",
                "execution-stop-time":"169",
                "execution-time":"91",
                "instructions-used":"63309",
                "table-name":"yahoo.finance.quotes"
            },
            "user-time":"170",
            "service-time":"154",
            "build-version":"0.2.100"
        },
        "results":{
            "quote":{
                "symbol":"2628.HK",
                "Ask":null,
                "AverageDailyVolume":"41728600",
                "Bid":null,
                "AskRealtime":null,
                "BidRealtime":null,
                "BookValue":"1.62",
                "Change_PercentChange":"-0.10 - -0.26%",
                "Change":"-0.10",
                "Commission":null,
                "Currency":"HKD",
                "ChangeRealtime":null,
                "AfterHoursChangeRealtime":null,
                "DividendShare":"0.38",
                "LastTradeDate":"4/28/2015",
                "TradeDate":null,
                "EarningsShare":"1.43",
                "ErrorIndicationreturnedforsymbolchangedinvalid":null,
                "EPSEstimateCurrentYear":"0.89",
                "EPSEstimateNextYear":null,
                "EPSEstimateNextQuarter":"0.00",
                "DaysLow":"37.50",
                "DaysHigh":"38.45",
                "YearLow":"19.72",
                "YearHigh":"41.00",
                "HoldingsGainPercent":null,
                "AnnualizedGain":null,
                "HoldingsGain":null,
                "HoldingsGainPercentRealtime":null,
                "HoldingsGainRealtime":null,
                "MoreInfo":null,
                "OrderBookRealtime":null,
                "MarketCapitalization":"1076.89B",
                "MarketCapRealtime":null,
                "EBITDA":"7.06B",
                "ChangeFromYearLow":"18.38",
                "PercentChangeFromYearLow":"+93.20%",
                "LastTradeRealtimeWithTime":null,
                "ChangePercentRealtime":null,
                "ChangeFromYearHigh":"-2.90",
                "PercebtChangeFromYearHigh":"-7.07%",
                "LastTradeWithTime":"3:59pm - 38.10",
                "LastTradePriceOnly":"38.10",
                "HighLimit":null,
                "LowLimit":null,
                "DaysRange":"37.50 - 38.45",
                "DaysRangeRealtime":null,
                "FiftydayMovingAverage":"35.06",
                "TwoHundreddayMovingAverage":"29.53",
                "ChangeFromTwoHundreddayMovingAverage":"8.57",
                "PercentChangeFromTwoHundreddayMovingAverage":"+29.01%",
                "ChangeFromFiftydayMovingAverage":"3.04",
                "PercentChangeFromFiftydayMovingAverage":"+8.67%",
                "Name":"CHINA LIFE",
                "Notes":null,
                "Open":"38.40",
                "PreviousClose":"38.20",
                "PricePaid":null,
                "ChangeinPercent":"-0.26%",
                "PriceSales":"15.18",
                "PriceBook":"23.55",
                "ExDividendDate":"6/3/2014",
                "PERatio":"26.78",
                "DividendPayDate":null,
                "PERatioRealtime":null,
                "PEGRatio":"0.00",
                "PriceEPSEstimateCurrentYear":null,
                "PriceEPSEstimateNextYear":null,
                "PriceEPSEstimateNextYear":null,
                "Symbol":"2628.HK",
                "SharesOwned":null,
                "ShortRatio":"0.00",
                "LastTradeTime":"3:59pm",
                "TickerTrend":null,
                "OneyrTargetPrice":null,
                "Volume":"35734538",
                "HoldingsValue":null,
                "HoldingsValueRealtime":null,
                "YearRange":"19.72 - 41.00",
                "DaysValueChange":null,
                "DaysValueChangeRealtime":null,
                "StockExchange":"HKG",
                "DividendYield":"1.23",
                "PercentChange":"-0.26%"
            }
        }
    }
}

只要在服務器設定 Cronjob 每天獲取數據,並儲存到資料庫,日後便能拿來使用。

2015年4月27日 星期一

信報:IT 企業須與時並進

文章及相片來源:
http://www1.hkej.com/dailynews/investment/article/1038836/I+T企業須與時並進
https://www.facebook.com/wtiawdc

傳統的市場調查行業,資料以紙筆記錄,例如貨品的價格、有效日期、擺放位置,而且每天要到不同位置零售店舖,因而需要大量人手。

2011年,高欣(Jacky)與拍檔何兆基(Pacess)成立彼岸石(BeyondZ Company Limited),開發流動應用軟件,並研發出流動市場調查平台,以手機程式取代紙筆來進行市場調查。所得資料,如缺貨情況及對手價格,即時整合成報告,省下不少資料輸入時間,有關人手的支出更減少了兩成。

這個全新營運系統,他們應用於自己公司,亦推廣予客戶,可惜現時只有約15至20%客戶採用。「客戶的管理層有一定年紀,我們推銷時,對方看不到重要性,只問系統會賺到多少錢。」

令服務更有效率
高欣認為,這種想法很可能導致這些公司數年後遭淘汰,因為對手正不斷求變,令服務更有效率。「5年前,客戶開始詢問流動裝置系統,這是由於iPhone興起,及網絡速度加快,我看到這個市場的潛質,便創立了彼岸石」。

彼岸石員工只得15人,其中更以「90後」居多。

高欣認為,作為「70後」創業者,自己本身亦注重紀律,但管理「90後」同事,可以較為task-oriented,不講究嚴守紀律,才能創造自由的環境讓他們發揮,因為用舊的一套,「90後」同事不會受落。

「我們會在辦公室內踢毽、射槍、一齊抽iPhone和演唱會飛,傳統公司不能光明正大做的,在新興行業卻很自由。」

「有一定年紀」的客戶加上「未夠一定年紀」的員工,都是本地中小企的大難題,高欣同時面對兩大難題,訪問中講得最多的是轉變和求變。在客戶面前他提出改變生產流程的要求,在「90後」員工面前他要求自己作出改變,這是他保持不被市場淘汰的方法。

2015年4月26日 星期日

Raspberry Pi 縮時攝影綠屏問題


拍攝了幾天縮時攝影;有黃昏的、有晚間的、有零晨的、有公司的、有在家的。它們都有一個共同之處。拍攝當中某些幀出了問題。有的是零檔案大小、有的是全畫面是綠色、有的是全畫面是白色、有的是全畫面是灰色、有的是全畫面是黑色。當問題出現了一會後,拍攝又能回復正常。暫時也不清楚問題的原因,但倒是有點頭緒。通常問題都發生在環境較光時,亦即是太陽伯伯已經起來的時候。拍攝夜景快門設定為四秒,當太陽出來時,這樣的暴光時間實在是太長。估計是太光導致零件出錯。希望之後能找出原因,修正問題。

2015年4月25日 星期六

Apple Watch 一天用後感


公司訂購的 Apple Watch Sport 38mm 昨天到手。我自己還未決定購買 Apple Watch 還是 Apple Watch Sport。不過,身邊的朋友及行家,個個都是選 Sport 版。主要是入門價錢較低,同時預計一年轉款一次,不願投資。似乎大家對 Apple Watch 都沒有信心,買了只是嘗試及為了開發需要。為了決定是否購買 Sport 版,於是把 Apple Watch Sport 連上了我的 iPhone 6 Plus 後試戴了一天:
 
  • Apple Watch 約左下午四時到手,當時電池約 73% 滿,到深夜十二時,還有 40% 左右。電池續航力滿意。基本上能運作 18-20 小時。
  • 推送通知很方便。但 GMail 推送通知字數少,基本上無法理解電郵內容,需要回到 iPhone 上處理。
  • 其他應用的實用性不大。如果 iPhone 上 WeChat 沒有在背景執行,Apple Watch 上是用不到。
  • Apple Watch 上的拍照應用是搖控器,有一點作用。
  • 內建的音樂程式同樣是搖控器,操作 iPhone 所播放的歌曲。在開始時,所有歌曲的圖片都不見了,需要幾個小時後才能顯示。可能是下載量比較多,而又同時需要兼顧省電原則吧。
  • Flipboard 花了很久仍然處於載入狀態。
  • 錶帶方面,用了一天後,已經有點污積。畢竟在電腦面前工作時,手會經常跟檯面磨擦。
  • 預測反應比想像中好很多。Apple Watch 在我想看時間的時候,會自行亮著屏幕。相信是跟看錶的角度及加速度而估計出來,非常精準。
  • 我在 Apple Watch 的語言設定為「繁體中文(香港)」,Siri 能準確地聆聽我所講的粵語。
  • 長密碼保護只能輸入數字,保安程度還是夠。在沒有輸入密碼的情況下,只能看錶面,不能做其他東西。
  • 2015年4月22日 星期三

    如何找出 Raspberry Pi 2 的 IP 地址


    朋友加入了 Raspberry Pi 的大家庭,問我如何在 RPi 2 沒有接上顯示屏時怎樣取得 IP 地址。於是我介紹他用 Fing。那是一個在 iOS 上跑的應用程式,能列出所在網絡的裝置。用戶還可以為每台裝置設定圖示及名稱,非常方便。我就是用 Fing 來監察公司及家裡裝置的地址。

    2015年4月21日 星期二

    Python 計時程式

    目前的拍照程式中,幀與幀之間只是用 time.sleep(15) 來做計時。這樣做並不準確。因為拍攝出來的畫面內容的不同,會有不同的壓縮需時,也會有不同的檔案體積,更會有不同的儲存時間。上一幀由拍攝到儲存同了五秒,下一幀可能會花上六秒。為了精準處理拍攝間距,等候的部份需要特別的處理:
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  Time Delay Test Version 1.00
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import time
    
    ##------------------------------------------------------------
    ##  Global variables
    delayInMillis = 2000
    
    ##------------------------------------------------------------
    ##  Function
    def getTimeMillis():
        millis = int(round(time.time()*1000))
        return millis
    
    ##============================================================
    ##  Program start
    ##============================================================
    print "Time Delay Test Version 1.00"
    
    millisBegin = getTimeMillis()
    print "Begin: "+str(millisBegin)
    
    ##  Do your own task here!
    time.sleep(1)
    
    ##------------------------------------------------------------
    ##  Calculate how long should be delayed
    millisDelay = millisBegin+delayInMillis-getTimeMillis()
    print "Delay for: "+str(millisDelay)
    
    ##  Wait until delay matches delayInMillis
    time.sleep(millisDelay/1000.0)
    
    ##  Calculate how long was delayed
    millisDifferent = getTimeMillis()-millisBegin
    print "Different: "+str(millisDifferent)

    2015年4月20日 星期一

    在 Raspberry Pi 2 上把串連的 JPG 轉換成 AVI

    嘗試了拍攝深夜 11:00 至 早上 08:00 的相片,共多達 1500 張。下一步是在 Raspberry Pi 上把串連的 .jpg 轉換成 .avi。首先安裝好需要的應用程式:
    pi@amigo-camera ~/timelapse $ sudo apt-get install mencoder
    pi@amigo-camera ~/timelapse $ sudo apt-get install ffmpeg

    轉換程式需要一個 .jpg 清單,它會按清單內的順序去壓成影片。所以要先製作清單:
    pi@amigo-camera ~/timelapse $ cd DCIM
    pi@amigo-camera ~/timelapse/DCIM $ ls *.jpg > mencoder.txt

    由於設定了 ISO 及快門時間,有部份相片相信曝光過短或過長。檢查過相片後在 mencoder.txt 中把它們拿走。之後就是正式的轉換步驟:
    pi@amigo-camera ~/timelapse/DCIM $ mencoder -nosound -ovc lavc -lavcopts vcodec=mpeg4:aspect=16/9:vbitrate=8000000 -vf scale=1920:1080 -o timelapse.avi -mf type=jpeg:fps=30 mf://@mencoder.txt
    
    MEncoder svn r34540 (Raspbian), built with gcc-4.6 (C) 2000-2012 MPlayer Team
    success: format: 16  data: 0x0 - 0x0
    MF file format detected.
    [mf] number of files: 290
    VIDEO:  [IJPG]  0x0  24bpp  30.000 fps    0.0 kbps ( 0.0 kbyte/s)
    [V] filefmt:16  fourcc:0x47504A49  size:0x0  fps:30.000  ftime:=0.0333
    libavcodec version 53.35.0 (external)
    Mismatching header version 53.32.2
    Opening video filter: [expand osd=1]
    Expand: -1 x -1, -1 ; -1, osd: 1, aspect: 0.000000, round: 1
    Opening video filter: [scale w=1920 h=1080]
    ==========================================================================
    Opening video decoder: [ffmpeg] FFmpeg's libavcodec codec family
    Selected video codec: [ffmjpeg] vfm: ffmpeg (FFmpeg MJPEG)
    ==========================================================================
    Movie-Aspect is undefined - no prescaling applied.
    [swscaler @ 0x158e540] BICUBIC scaler, from yuv420p to yuv420p using C
    videocodec: libavcodec (1920x1080 fourcc=34504d46 [FMP4])
    Writing header...
    ODML: vprp aspect is 16:9.
    Writing header...
    ODML: vprp aspect is 16:9.
    Pos:   9.7s    290f (100%)  0.34fps Trem:   0min   9mb  A-V:0.000 [7856:0]
    
    Flushing video frames.
    Writing index...
    Writing header...
    ODML: vprp aspect is 16:9.
    
    Video stream: 7856.798 kbit/s  (982099 B/s)  size: 9493631 bytes  9.667 secs  290 frames
    留意!在執行 mencoder 時必須跟 .jpg 的目錄,不然會有錯誤訊息。

    2015年4月19日 星期日

    解決 raspistill 停止問題

    纏擾了六天的 raspistill 問題,終於解決了。

    今天繼續處理 raspistill。由於嘗試過多方面的改動都未能解決 raspistill 停止問題,於是我把系統重灌。為了盡量保持系統的潔淨度,重灌後只作了 apt-get update, apt-get upgrade 及 rpi-update,沒有修正 WiFi 熱點用的 hostapd 及安裝 Nginx。但停止問題依然存在。

    然而,家中的工具較多,測試時我都喜歡為 Raspberry Pi 接上螢幕及鍵盤,方便直接處理,因而發現到解決問題的方法。重灌我嘗試了直接在終端上執行 raspistill。情況十分良好,用了 30 分鐘拍了超過 120 張照片仍然運作正常。於是讓系統繼續工作,一會兒再看,螢幕進入了保護模式。拍打空白鍵後,拍照工作仍然繼續,不過畫面上的訊息有點不一樣,像是拍照由 #### 繼續之類的訊息。意味著由進入螢幕保護模式理離開時的拍照編號會被跳過,實際情況也是如此。

    這個情況讓我聯想到 raspistill 停止問題可能跟螢幕保護模式有關。最後找到了相關設定:
    pi@amigo-camera ~ $ sudo nano /etc/kbd/config
    # screen blanking timeout.  monitor remains on, but the screen is cleared to
    # range: 0-60 min (0==never)  kernels I've looked at default to 10 minutes.
    # (see linux/drivers/char/console.c)
    BLANK_TIME=0
    
    # Powerdown time.  The console will go to DPMS Off mode POWERDOWN_TIME
    # minutes _after_ blanking.  (POWERDOWN_TIME + BLANK_TIME after the last input)
    POWERDOWN_TIME=0
    其實主要修改「BLANK_TIME」值。為了安全起見,我把上面兩個設定為零,問題便能解決。

    參考網址:
    https://www.raspberrypi.org/forums/viewtopic.php?f=28&t=92477&p=648825

    2015年4月18日 星期六

    未解決的 raspistill bug 問題

    打算用 Raspberry Pi 2 及 NoIR 鏡頭做 Timelapse 連拍,可是拍了半小時到兩小時會停止拍攝。困擾了五天還是無法解決。

    起初以 Python 程序及 Picamera 程序庫進行拍攝。每拍完一張便用 time.sleep(10) 等十秒再拍下一張的無限迴圈。而 Python 程序是由 /etc/rc.local 在啟動後執行。拍到兩小時左右,JPG 圖檔沒有再增加,意味著拍照出現問題。利用 ps aux 查看,Python 的程序還在執行。利用自接的實體鍵也能順利關機。這組關機程式正是 Python 中的程序,即是說 Python 確實在執行。

    這樣的話,有可能性是 Picamera 程序庫本身的缺陷而導致。於是改為在重啟後由 Bash 以 raspistill 的 Timelapse 形式執行。
    pi@raspberrypi ~/timelapse$ raspistill -o /home/pi/timelapse/RPi_%05d.jpg -vf -hf -ISO 800 -w 1024 -h 768 -ss 3000000 -t 57600000 -tl 15000 &
    可惜同樣在拍攝一段時間後 .jpg 沒有再新增,而且還留下了 RPi_00340.jpg~ 暫存檔案。

    查找過 /var/log/syslog 沒有錯誤訊息。毫無頭緒,唯有繼續再試。在網上找到資料說執行 raspistill 時可以加「 > log.txt &2>1」記錄錯誤訊息。同樣有試,同樣沒有輸出。

    既然連拍模式不行,那改為由 cronjob 呼叫 raspistill 拍單照總可以吧?!於是著手更改相關設定。不幸地,cronjob 也遇到相同情況。有在執行卻拍不出相片。

    也試過在 raspistill 後加入 -n 來取消預覽圖;連 apt-get update、apt-get upgrade、rpi-update 都試過,問題仍在。

    後來在這個網頁發現有差不多的情況發生。於是引發起我在問題發生時,在終端機上直接輸入指令看看效果。
    pi@raspberrypi ~/timelapse$ raspistill -n -o /home/pi/timelapse/Rpi.jpg -vf -hf -ISO 800 -w 1024 -h 768 -ss 3000000
    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
    今次終於有點錯誤指示了。轉而想到會否因為 SD 卡速度慢,在儲存相片時被後來的影像重疊在一起而出問題;於是把拍照的相距時間調到 30 秒。可惜情況一樣...。

    在網上找到 raspistill 的 -v 選項能顯示情況,試一試後多了一點眉目:
    Opening output file /home/pi/timelapse/20150418/20150418a_00118.jpg
    Enabling encoder output port
    Starting capture 118
    Finished capture 118
    Opening output file /home/pi/timelapse/20150418/20150418a_00119.jpg
    Enabling encoder output port
    Starting capture 119
    Finished capture 119
    Opening output file /home/pi/timelapse/20150418/20150418a_00120.jpg
    Enabling encoder output port
    在拍第 120 張相片時,沒有發生「Starting capture 120」...。有人說是 GPU 的記憶體分割不足。我檢查過預設值是:
    pi@raspberrypi ~ $ vcgencmd get_mem gpu
    gpu=64M
    於是改為 384MB。Raspberry Pi 有 1GB RAM,而這個硬件的用途是拍照,那麼把預設的 64MB 提升到 384MB 也不會影響運作:
    pi@raspberrypi ~ $ sudo nano /boot/config.txt
    ##  Pacess customise settings
    disable_camera_led=1
    
    #  Minimum GPU memory for camera use
    gpu_mem=384
    pi@raspberrypi ~ $ vcgencmd get_mem gpu
    gpu=384M
    留意「gpu_mem=384」是不能有空白。不過情況沒有改變...。
    pi@raspberrypi ~ $ vcgencmd version
    Feb 14 2015 22:20:17 
    Copyright (c) 2012 Broadcom
    version 7789db485409720b0e523a3d6b86b12ed56fd152 (clean) (release)
    pi@raspberrypi ~ $ vcgencmd get_camera
    supported=1 detected=1
    pi@raspberrypi ~ $ vcgencmd get_mem gpu
    gpu=384M
    pi@raspberrypi ~ $ vcgencmd otp_dump
    08:00000000
    09:00000000
    10:00000000
        ::    ::
    30:00a21041
        ::    ::
    62:00000000
    63:00000000
    64:00000000
    pi@raspberrypi ~ $ cat /proc/cpuinfo | tail -2
    Revision : a21041
    Serial  : 0000000018c66ea8

    2015年4月17日 星期五

    兩年了


    又一個風和日麗的早上。Sita 陳僖儀已經離開我們有兩年了。時間真是可怕的東西。兩年前的今天,我的 Facebook 被陳僖儀洗板。這是我第一次聽到「陳僖儀」這個名字。朋友都很惋惜陳僖儀的離世。吸引我點擊了在 Facebook 的 MV 貼文「忘川」。自此觸發了我繼續尋找 Sita 的往事。越了解越欣賞她。兩年後的今天,同樣的日子、同樣的 Facebook、同樣的被陳僖儀洗板。原本沒有打算今天去探 Sita,但在科學園的業務要急著找人,碰巧在今天面見應徵者。上天賜了籍口給我。

    今早除了我之外,還見到阿儀及她的另外兩位朋友帶著豐富的祭品。看來今天這個特別的日子有很多人前來拜祭。靈前放了超級多的「叮噹」、一份蛋撻、一盒維他檸檬茶。十四粒小叮噹,伴隨著一個大叮噹。意味著妳有十四門徒嗎?哈~

    回想一下,本來有很多機會見到妳,可惜沒有緣份。妳在 a.p.m. 演出,我卻遇不到;朋友公司搞的堆沙活動,妳是表演嘉賓,卻因外遊而參加不了;客戶 B.Duck 在尖沙咀搞的活動,妳也是表演嘉賓,可惜我沒有出席;客戶 Chivas 在酒吧搞的活動,妳同樣是表演嘉賓,可惜我也沒有出席;甚至是太陽娛樂找我們傾談開發手機 App,可惜談的不是 Sita 手機 App。如果有叮噹時光機,真的好想回到當時感受一下現場的氣氛。Read-only 也好吧!

    P.S.: 中午再去探一探 Sita。阿儀她們還在,而地上卻多了四束鮮花。

    2015年4月16日 星期四

    《Sita Chan Foundation 陳僖儀慈善基金》第一次正式活動


    上周六,雨天。出席了《Sita Chan Foundation 陳僖儀慈善基金》第一次正式活動;是為「基督教香港信義會」賣旗籌款。款項是為該會「戒毒服務」作為經費之用。活動相片也在 Facebook 專頁 https://www.facebook.com/sitachanfoundation/posts/673109632816437 發佈了。

    2015年4月15日 星期三

    MacBook + CUPS + Epson TM-T70


    收到合作單位送來的掃描器及打印機,上頭指示希望能在下周二能完成示範程序。簡單測試過掃描器,跟以往的一樣,直插直用,沒有問題。然而打印機不像以往測試過的 Zebra 牌子的做法,要研究研究。

    目標要完成的示範程序很簡單。我先從平台入手。Windows, Mac, Linux 佳可。但要能成功打印,令到難度提升。預計 Windows 出現最多狀況之餘,又要安裝驅動程式;搞不好只有 Windows 7 的沒有 Windows 8 的,程序也要以 C++ 編寫,還是不選為妙。Mac 及 Linux 都是很好的選擇,原本選 Linux,估計能在 Raspberry Pi 上跑,外出做示範時機器細多了、輕多了。不過,如果我不在場,出了狀況並不易處理。最後落入 Mac 的懷抱。

    我不想在 Mac 上安裝驅動程式,合作單位指示用 CUPS 及 lpr 指令,於是遵從教導逐一摸索。原來 Mac 本身裝有 CUPS 卻沒有打開,需要以 root 身份處理:


    完成後在瀏覽器的地址列輸入「http://localhost:631/」會出現以下畫面:

    點選「Add Printers and Classes」後點左上角的「Add Printer」:


    由於會用 lpr 指令,所以這裡選「LPD/LPR Host or Printer」:


    按照畫面不同的連接指示,我揀選了 LPD 格式,輸入「lpd://192.168.192.168/queue/」。當中「192.168.192.168」是打印機的地址:


    為打印機設定名稱。名稱將會在 lpr 指令中用到。大小寫不拘。完成所有欄位輸入後點「Continue」:


    原來我在「Make」選了「Epson」,但之後的清單全是普通打印機,沒有手上這台出收據的打印機;又不想安裝 PPD 驅動程式,所以還是選了「RAW」後點「Continue」:


    沒有其他東西需要設定,點「Continue」:


    同樣不需要設定,點「Set Default Options」:


    完成新增打印機動作:


    等一會會自動跳到打印工作畫面。這時可以點左上角「Maintenance」選單中的「Test Print」確認打印機運作無誤:


    回到終端機。我簡單地準備了 test.txt,內容是「Hello World!」,以「lpr -P epson test.txt」把內容打印出來:


    成功!以 PHP 的「shell_exec("lpr -P epson test.txt")」測試同樣有效。所有功能齊備,之後便是製作介面的功夫...。

    2015年4月14日 星期二

    Python + PS3 DualShock

    配對好 PS3 的 DualShock 手掣後要如何編程呢?其實用 Python 就可以了:
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  PS3 DualShock Joystick Test Version 1.00
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import RPi.GPIO as GPIO
    import pygame
    import time
    import os
    
    ##------------------------------------------------------------
    ##  Global variables
    pinMotorL0 = 31
    pinMotorL1 = 33
    pinMotorR0 = 35
    pinMotorR1 = 37
    
    leftStatus = 0
    rightStatus = 0
    
    threshold = 0.60
    
    ##============================================================
    ##  Program start
    ##============================================================
    GPIO.setmode(GPIO.BOARD)
    
    ##  This is important, tell pygame no display needed
    os.environ["SDL_VIDEODRIVER"] = "dummy"
    
    ##  Initialise the pygame library
    pygame.init()
    
    ##  Connect to the first joystick lazily, should be check with name
    joystick = pygame.joystick.Joystick(0)
    joystick.init()
    
    ##------------------------------------------------------------
    ##  Setup pins
    GPIO.setup(pinMotorL0, GPIO.OUT)
    GPIO.setup(pinMotorL1, GPIO.OUT)
    GPIO.setup(pinMotorR0, GPIO.OUT)
    GPIO.setup(pinMotorR1, GPIO.OUT)
    
    GPIO.output(pinMotorL0, False)
    GPIO.output(pinMotorL1, False)
    GPIO.output(pinMotorR0, False)
    GPIO.output(pinMotorR1, False)
    
    ##------------------------------------------------------------
    print "Initialized joystick: %s" % joystick.get_name()
    try:
        ##  This is the main loop
        while True:
    
            ##  Check for any queued events and then process each one
            events = pygame.event.get()
            for event in events:
    
                ##  Check if one of the joysticks has moved
                if event.type == pygame.JOYAXISMOTION:
    
                    ##------------------------------------------------------------
                    ##  Left mushroom
                    if event.axis == 1:
                        if (event.value > threshold):
                            if (leftStatus != 1):
                                GPIO.output(pinMotorL0, False)
                                GPIO.output(pinMotorL1, True)
                                print "Left up"
                                leftStatus = 1
    
                        elif (event.value < -threshold):
                            if (leftStatus != 2):
                                GPIO.output(pinMotorL0, True)
                                GPIO.output(pinMotorL1, False)
                                print "Left down"
                                leftStatus = 2
    
                        elif (event.value <= threshold):
                            if (leftStatus != 0):
                                GPIO.output(pinMotorL0, False)
                                GPIO.output(pinMotorL1, False)
                                print "Left centered"
                                leftStatus = 0
    
                    ##------------------------------------------------------------
                    ##  Right mushroom
                    elif event.axis == 3:
                        if (event.value > threshold):
                            if (rightStatus != 1):
                                GPIO.output(pinMotorR0, False)
                                GPIO.output(pinMotorR1, True)
                                print "Right up"
                                rightStatus = 1
    
                        elif (event.value < -threshold):
                            if (rightStatus != 2):
                                GPIO.output(pinMotorR0, True)
                                GPIO.output(pinMotorR1, False)
                                print "Right down"
                                rightStatus = 2
    
                        elif (event.value <= threshold):
                            if (rightStatus != 0):
                                GPIO.output(pinMotorR0, False)
                                GPIO.output(pinMotorR1, False)
                                print "Right centered"
                                rightStatus = 0
    
    ##------------------------------------------------------------
    except KeyboardInterrupt:
        joystick.quit()
        GPIO.output(pinMotorL0, False)
        GPIO.output(pinMotorL1, False)
        GPIO.output(pinMotorR0, False)
        GPIO.output(pinMotorR1, False)
        GPIO.cleanup()
    我這個程式接駁到一架沒有屏幕的坦克玩具。留意「os.environ["SDL_VIDEODRIVER"] = "dummy"」這句很重要,不然在執行這個 Python 程式時會出現「找不到屏幕」錯誤。

    主軸對照:
    Axis 0 = 左磨菇掣的橫軸
    Axis 1 = 左磨菇掣的蹤軸
    Axis 2 = 右磨菇掣的橫軸
    Axis 3 = 右磨菇掣的蹤軸

    按鍵對照:
    0 = Select
    1 =
    2 =
    3 = Start
    4 = Digital Up
    5 = Digital Right
    6 = Digital Down
    7 = Digitial Left
    8 = L2 (digital mode)
    9 = R2 (digital mode)
    10 = R1
    11 = L1
    12 = Triangle
    13 = Circle
    14 = X
    15 = Square
    16 = PS
    17 =
    18 =

    2015年4月13日 星期一

    Bluetooth USB + RPi2 + PS3 Dualshock


    新機體打算用 Raspberry Pi 2 作為主板。操控方面,原先打算用智能手機接駁 RPi 的 WiFi 熱點,然後透過在 RPi 上的網頁進行操作。可是擔心反應不夠快及會有訊號遺失的問題,所以在想有甚麼其他的可能性。編寫手機程式用 Socket 溝通是其中一個;利用機械人界慣常使用的 PS2 手掣又是一個。在尋找 RPi 對應 PS2 手掣的過程中,發現了 RPi 加上藍牙 2.0 USB 後便能直接跟 PS3 Sixaxis DualShock 通訊,不像 PS2 要加入其他線路,於是嘗試了一下。

    首先進行更新:
    pi@raspberrypi ~ $ sudo apt-get update
    Get:1 http://mirrordirector.raspbian.org wheezy Release.gpg [490 B]
    Get:2 http://raspberrypi.collabora.com wheezy Release.gpg [836 B]
    Get:3 http://mirrordirector.raspbian.org wheezy Release [14.4 kB]
        ::    ::    ::    ::
    Ign http://mirrordirector.raspbian.org wheezy/non-free Translation-en
    Ign http://mirrordirector.raspbian.org wheezy/rpi Translation-en
    Fetched 7001 kB in 23s (302 kB/s)
    Reading package lists... Done

    安裝藍牙相關的工具:
    pi@raspberrypi ~/ps3_sixpair $ sudo apt-get install bluez-utils bluez-compat bluez-hcidump checkinstall libusb-dev  libbluetooth-dev joystick
    Reading package lists... Done
    Building dependency tree       
    Reading state information... Done
        ::    ::    ::    ::
    Setting up inputattach (1:1.4.3-1) ...
    Setting up joystick (1:1.4.3-1) ...
    Setting up libbluetooth-dev (4.99-2) ...

    插入藍牙 USB 並檢查是否跟 RPi 對應。出現如以下文字代表對應:
    pi@raspberrypi ~/ps3_sixpair $ hciconfig
    hci0: Type: BR/EDR  Bus: USB
     BD Address: 11:11:11:11:11:11  ACL MTU: 678:8  SCO MTU: 48:10
     UP RUNNING PSCAN 
     RX bytes:1257 acl:0 sco:0 events:48 errors:0
     TX bytes:458 acl:0 sco:0 commands:48 errors:0

    下載 PS3 手掣配對程式:
    pi@raspberrypi ~ $ mkdir ps3_sixpair
    pi@raspberrypi ~ $ cd ps3_sixpair/
    pi@raspberrypi ~/ps3_sixpair $ wget http://www.pabr.org/sixlinux/sixpair.c
    --2015-04-10 22:23:34--  http://www.pabr.org/sixlinux/sixpair.c
    Resolving www.pabr.org (www.pabr.org)... 62.210.16.61
    Connecting to www.pabr.org (www.pabr.org)|62.210.16.61|:80... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 4022 (3.9K) [text/x-csrc]
    Saving to: `sixpair.c'
    
    100%[========================================================>] 4,022     --.-K/s  in 0s
    
    2015-04-10 22:23:35 (45.1 MB/s) - `sixpair.c' saved [4022/4022]

    編譯 PS3 手掣配對程式:
    pi@raspberrypi ~/ps3_sixpair $ gcc -o sixpair sixpair.c -lusb

    下載 PS3 手掣通訊程式:
    pi@raspberrypi ~/ps3_sixpair $ wget http://sourceforge.net/projects/qtsixa/files/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz
    --2015-04-10 22:40:50--  http://sourceforge.net/projects/qtsixa/files/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz
    Resolving sourceforge.net (sourceforge.net)... 216.34.181.60
    Connecting to sourceforge.net (sourceforge.net)|216.34.181.60|:80... connected.
    HTTP request sent, awaiting response... 302 Found
    Location: http://sourceforge.net/projects/qtsixa/files/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz/download [following]
    --2015-04-10 22:40:51--  http://sourceforge.net/projects/qtsixa/files/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz/download
    Connecting to sourceforge.net (sourceforge.net)|216.34.181.60|:80... connected.
    HTTP request sent, awaiting response... 302 Found
    Location: http://downloads.sourceforge.net/project/qtsixa/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz?r=&ts=1428676851&use_mirror=ncu [following]
    --2015-04-10 22:40:51--  http://downloads.sourceforge.net/project/qtsixa/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz?r=&ts=1428676851&use_mirror=ncu
    Resolving downloads.sourceforge.net (downloads.sourceforge.net)... 216.34.181.59
    Connecting to downloads.sourceforge.net (downloads.sourceforge.net)|216.34.181.59|:80... connected.
    HTTP request sent, awaiting response... 302 Found
    Location: http://ncu.dl.sourceforge.net/project/qtsixa/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz [following]
    --2015-04-10 22:40:52--  http://ncu.dl.sourceforge.net/project/qtsixa/QtSixA%201.5.1/QtSixA-1.5.1-src.tar.gz
    Resolving ncu.dl.sourceforge.net (ncu.dl.sourceforge.net)... 140.115.17.45
    Connecting to ncu.dl.sourceforge.net (ncu.dl.sourceforge.net)|140.115.17.45|:80... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 930296 (908K) [application/x-gzip]
    Saving to: `QtSixA-1.5.1-src.tar.gz'
    
    100%[========================================================>] 930,296    964K/s  in 0.9s
    
    2015-04-10 22:40:53 (964 KB/s) - `QtSixA-1.5.1-src.tar.gz' saved [930296/930296]
    
    pi@raspberrypi ~/ps3_sixpair $ tar xfvz QtSixA-1.5.1-src.tar.gz
    QtSixA-1.5.1/
    QtSixA-1.5.1/qtsixa/
    QtSixA-1.5.1/qtsixa/game-profiles/
    QtSixA-1.5.1/qtsixa/game-profiles/etracer_accel
        ::    ::    ::
    QtSixA-1.5.1/INSTALL
    QtSixA-1.5.1/Makefile
    QtSixA-1.5.1/README
    QtSixA-1.5.1/TODO
    QtSixA-1.5.1/manual.pdf
    pi@raspberrypi ~/ps3_sixpair $ cd QtSixA-1.5.1/sixad

    編譯 PS3 手掣通訊程式:
    pi@raspberrypi ~/ps3_sixpair/QtSixA-1.5.1/sixad $ make
    mkdir -p bins
    g++ -O2 -Wall -Wl,-Bsymbolic-functions sixad-bin.cpp bluetooth.cpp shared.cpp textfile.cpp -o bins/sixad-bin `pkg-config --cflags --libs bluez` -lpthread -fpermissive
    sixad-bin.cpp: In function 'int main(int, char**)':
    sixad-bin.cpp:84:20: warning: taking address of temporary [-fpermissive]
    g++ -O2 -Wall -Wl,-Bsymbolic-functions sixad-sixaxis.cpp sixaxis.cpp shared.cpp uinput.cpp textfile.cpp -o bins/sixad-sixaxis -lpthread -lrt
    g++ -O2 -Wall -Wl,-Bsymbolic-functions sixad-remote.cpp remote.cpp shared.cpp uinput.cpp textfile.cpp -o bins/sixad-remote -lrt
    g++ -O2 -Wall -Wl,-Bsymbolic-functions sixad-raw.cpp sixaxis.cpp shared.cpp uinput.cpp textfile.cpp -o bins/sixad-raw
    g++ -O2 -Wall -Wl,-Bsymbolic-functions sixad-3in1.cpp sixaxis.cpp shared.cpp uinput.cpp textfile.cpp -o bins/sixad-3in1

    安裝 PS3 手掣通訊程式:
    pi@raspberrypi ~/ps3_sixpair/QtSixA-1.5.1/sixad $ sudo checkinstall
    
    checkinstall 1.6.2, Copyright 2009 Felipe Eduardo Sanchez Diaz Duran
               This software is released under the GNU GPL.
    
    
    The package documentation directory ./doc-pak does not exist. 
    Should I create a default set of package docs?  [y]: y
    
    Preparing package documentation...OK
    
    *** No known documentation files were found. The new package 
    *** won't include a documentation directory.
    
    Please write a description for the package.
    End your description with an empty line or EOF.
    >> PS3 Controller for Raspberry Pi
    >> 
    
    *****************************************
    **** Debian package creation selected ***
    *****************************************
    
    This package will be built according to these values: 
    
    0 -  Maintainer: [ root@raspberrypi ]
    1 -  Summary: [ PS3 Controller for Raspberry Pi ]
    2 -  Name:    [ sixad ]
    3 -  Version: [ 20150410 ]
    4 -  Release: [ 1 ]
    5 -  License: [ GPL ]
    6 -  Group:   [ checkinstall ]
    7 -  Architecture: [ armhf ]
    8 -  Source location: [ sixad ]
    9 -  Alternate source location: [  ]
    10 - Requires: [  ]
    11 - Provides: [ sixad ]
    12 - Conflicts: [  ]
    13 - Replaces: [  ]
    
    Enter a number to change any of them or press ENTER to continue: 
    
    Installing with make install...
    
    ========================= Installation results ===========================
    install -d /etc/default/
    install -d /etc/init.d/
    install -d /etc/logrotate.d/
    install -d /usr/bin/
    install -d /usr/sbin/
    install -d /var/lib/sixad/
    install -d /var/lib/sixad/profiles/
    install -m 644 sixad.default /etc/default/sixad
    install -m 755 sixad.init /etc/init.d/sixad
    install -m 644 sixad.log /etc/logrotate.d/sixad
    install -m 755 sixad /usr/bin/
    install -m 755 bins/sixad-bin /usr/sbin/
    install -m 755 bins/sixad-sixaxis /usr/sbin/
    install -m 755 bins/sixad-remote /usr/sbin/
    install -m 755 bins/sixad-3in1 /usr/sbin/
    install -m 755 bins/sixad-raw /usr/sbin/
    install -m 755 sixad-dbus-blocker /usr/sbin/
    Installation is Complete!
    
    ======================== Installation successful ==========================
    
    Copying files to the temporary directory...OK
    
    Stripping ELF binaries and libraries...OK
    
    Compressing man pages...OK
    
    Building file list...OK
    
    Building Debian package...OK
    
    Installing Debian package...OK
    
    Erasing temporary files...OK
    
    Writing backup package...OK
    OK
    
    Deleting temp dir...OK
    
    
    **********************************************************************
    
     Done. The new package has been installed and saved to
    
     /home/pi/ps3_sixpair/QtSixA-1.5.1/sixad/sixad_20150410-1_armhf.deb
    
     You can remove it from your system anytime using: 
    
          dpkg -r sixad
    
    **********************************************************************

    把 PS3 手掣以 USB 線接到 RPi 進行測試對接:
    pi@raspberrypi ~/ps3_sixpair/QtSixA-1.5.1/sixad $ sudo sixad -start
    sixad-bin[3370]: started
    sixad-bin[3370]: sixad started, press the PS button now
    sixad-bin[3370]: unable to connect to sdp session
    sixad-bin[3370]: Connected Sony Computer Entertainment Wireless Controller (0A:C6:3E:4D:2C:86)

    成功後,可以執行 justest 檢查按鍵情況:
    pi@raspberrypi ~/ps3_sixpair/QtSixA-1.5.1/sixad $ sudo jstest
    
    Axes:  0:     0  1:     0  2:     0  3:     0  4:-32767  5:-32767  6:-32767  7:-32767  8:-32767  9:-32767 10:-32767 11:-32767 12:-32767 13:-32767 14:-32767 15:-32767 16:-32767 17:-32767 18:-32767 19:-32767 20:-32767 21:-32767 22:-32767 23:-32767 24:-32767 25:-32767 26:-32767
    
    Buttons:  0:off  1:off  2:off  3:off  4:off  5:off  6:off  7:off  8:off  9:off 10:off 11:off 12:off 13:off 14:off 15:off 16:off 17:off 18:off

    要是想在重新開機後配對 PS3 手掣,就執行:
    pi@raspberrypi ~/ps3_sixpair/QtSixA-1.5.1/sixad $ sudo update-rc.d sixad defaults

    參考網址:
    http://www.raspians.com/trying-again/
    http://booting-rpi.blogspot.ro/2012/08/dualshock-3-and-raspberry-pi.html
    https://github.com/petrockblog/RetroPie-Setup/wiki/Setting-up-a-PS3-controller

    2015年4月12日 星期日

    開啟 Raspberry Pi 上 Nginx 的目錄列表功能

    當利用 AMIGO Camera 拍攝了一大堆照片後,現在是透過 FTP/SSH 方式把 .jpg 拷到電腦;但這畢竟需要在電腦上做才方便。如果用 HTTP 網頁方式呈然則會方便很多,只要把平板電腦或手機接駁 AMIGO Camera 的 WiFi 連接點,再用瀏覽器打開相機內的頁面,便能看到所有相片。不過,又要設計頁面、又要編程,目前還是偷懶狀態,簡簡單單選用 Nginx 本身的目錄列表功能比較好。

    首先在 Nginx 的網頁根目錄下建立 Symbolic Link 捷徑:
    pi@raspberrypi ~ $ cd /usr/share/nginx/www
    pi@raspberrypi /usr/share/nginx/www $ ln -s /home/pi/camera/ camera
    這樣在瀏覽器輸入「http://192.168.1.1/camera/DCIM/」便能直接存取 /home/pi/camera/DCIM 內的檔案。不過,Nginx 預設把目錄列表功能關閉。除非你知道目錄中指定的檔案名稱,否則是無法讀取。解決方法是在 Nginx 加入目錄設定,讓指定目錄能執行列表功能:
    pi@raspberrypi ~ $ cd /etc/nginx/conf.d
    pi@raspberrypi /etc/nginx/conf.d $ sudo nano camera.conf
    輸入以下內容:
    server  {
            location /camera/DCIM  {
                    root /home/pi/;
                    autoindex on;
            }
    }
    重新啟動 Nginx:
    pi@raspberrypi /etc/nginx/conf.d $ sudo service nginx restart

    2015年4月11日 星期六

    AMIGO Camera 開發記錄


    經過細心的修正,紅外線相機的頂蓋終於能較為平滑地打印出來。外殻已經完成,線亦配好了,按鍵測試也完成了。接著是真正的編程部份。我的紅外線相機設計有兩顆按鈕:一顆用來拍攝;另一顆用來關機。畢竟 Raspberry Pi 是跑 Linux,不正當關機有可能導致下一次無法正常開機,還是小心駛得萬年船。

    之前三個測試程式已經包含了影相程式的核心功能,還欠關機的程式。這個非常簡單,用一句指令就能達到:
    os.system("shutdown now -h")
    現在各個功能都測試好了,正式將所有東西拼在一起:
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  AMIGO Camera Version 1.00
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import RPi.GPIO as GPIO
    import picamera
    import os.path
    import time
    
    ##------------------------------------------------------------
    ##  Global variables
    counter = 1
    counterFile = ".counter"
    folder = "/home/pi/camera/DCIM/"
    
    camera = picamera.PiCamera()
    camera.led = False
    
    ##------------------------------------------------------------
    ##  Function
    def captureNow(channel):
        global camera
        global counter
    
        ##with picamera.PiCamera() as camera:
        filename = folder+"RPi_{0:05d}.jpg".format(counter)
        print "Capturing "+filename+"..."
    
        try:
            camera.led = True
            camera.rotation = 180
            camera.resolution = (2592, 1944)
            camera.exif_tags["IFD0.Copyright"] = "Copyright (c) 2015 Pacess Studio.  All rights reserved."
            camera.exif_tags["EXIF.UserComment"] = "AMIGO Camera"
            camera.capture(filename)
            camera.led = False
    
            ##  Update counter to file
            counter = counter+1
            file = open(folder+counterFile, "wb")
            file.write(str(counter))
            file.close()
    
        finally:
            print "Done"
    
    ##------------------------------------------------------------
    def shutDownNow(chanel):
        print "Shutdown now..."
        os.system("shutdown now -h")
    
    ##============================================================
    ##  Program start
    ##============================================================
    print "AMIGO Camera Version 1.00"
    
    ##------------------------------------------------------------
    ##  Check if file exists
    isFile = os.path.isfile(folder+counterFile)
    if isFile:
    
        ##  Read file content
        with open(folder+counterFile) as file:
            counter = int(file.read())
    
    print "Counter: "+str(counter)
    
    ##------------------------------------------------------------
    ##  Setup pins
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    
    GPIO.add_event_detect(20, GPIO.RISING, callback=captureNow, bouncetime=2000)
    GPIO.add_event_detect(21, GPIO.RISING, callback=shutDownNow, bouncetime=2000)
    
    print "Setup GPIO done, ready..."
    
    ##------------------------------------------------------------
    ##  Main loop
    try:
        while True:
            time.sleep(1)
    
    finally:
        ##------------------------------------------------------------
        ##  Clean up
        GPIO.remove_event_detect(21)
        GPIO.remove_event_detect(20)
    
    GPIO.cleanup()
    考慮到有機會在很短的時間內拍攝多張相片,我把本來官方範例中的熱身時間取消;同時把「with picamera.PiCamera() as camera:」這種寫法改為「camera = picamera.PiCamera()」廣域變量,使得程式不用每次都重新設定鏡頭,也不用熱身時間。不過這個做法會令鏡頭旁的紅燈長時間亮起,因此在程式開首加入了:
    camera.led = False
    原本 folder 的值是 Relative,但發現系統會找不到目錄;要改為完全路徑來解決問題。
    folder = "/home/pi/camera/DCIM/"
    
    為了在 Raspberry Pi 啟動後知道 AMIGO Camera 有在執行,因此在程式中加入了幾句 print 指令,順便能檢視數值有沒有不妥的地方。

    最後就是開機設定。要令 Python 程式能在啟動時執行,我利用了 /etc/rc.local:
    #!/bin/sh -e
    #
    # rc.local
    #
    # This script is executed at the end of each multiuser runlevel.
    # Make sure that the script will "exit 0" on success or any other
    # value on error.
    #
    # In order to enable or disable this script just change the execution
    # bits.
    #
    # By default this script does nothing.
    
    # Print the IP address
    _IP=$(hostname -I) || true
    if [ "$_IP" ]; then
      printf "My IP address is %s\n" "$_IP"
    fi
    
    /home/pi/camera/bootup.sh &
    
    exit 0
    
    我沒有直接在 rc.local 執行拍照程式,改為執行拍照程式目錄內的 bootup.sh。這個 bootup.sh 內才執行拍照程式,為的是比較有結構地管理及備份。
    #!/bin/sh
    sudo python /home/pi/camera/camera.py

    2015年4月10日 星期五

    Python 拍攝程式

    最後還有一樣東西測試,要讓 Python 把拍攝後的畫面儲存成 .jpg 檔案:
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  AMIGO Camera Capture Test
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import picamera
    import os.path
    import time
    
    ##------------------------------------------------------------
    counter = 1
    counterFile = ".counter"
    folder = "DCIM/"
    
    ##------------------------------------------------------------
    ##  Check if file exists
    isFile = os.path.isfile(folder+counterFile)
    if isFile:
    
        ##  Read file content
        with open(folder+counterFile) as file:
            counter = int(file.read())+1
    
    ##  Update counter to file
    file = open(folder+counterFile, "wb")
    file.write(str(counter))
    file.close()
    
    ##------------------------------------------------------------
    with picamera.PiCamera() as camera:
        ##  camera.resolution = (2592, 1944)
        camera.resolution = (640, 480)
        ##camera.led = True
    
        ##  Camera warm-up time
        time.sleep(1)
    
        filename = folder+"RPi_{0:05d}.jpg".format(counter)
        camera.capture(filename)

    結合了計數器程式,每執行一次拍照後,計數器會加一;計數器的值會儲存在 .counter 檔案內,就算關機再開也不會令數值重設;而數值會用來作為輸出的 .jpg 檔名。

    2015年4月9日 星期四

    Python 按鍵檢測程式


    完成了計數器程式後,接著是測試按鍵的程式。程式用來確保接線無誤之外,同時測試怎樣檢測會來得順暢,甚至是確保按鍵時的噪訊處理。以前開發遊戲程式時,在讀取搖桿訊號時會出現噪訊。例如按鍵時,從系統收到的訊號很多時候會像是 0000010101111111,而不會是 000000111111 這麼乾淨。這是硬件無法處理的情況,需要從軟件方面修正。Raspberry Pi 同樣有這種情況。

    兩顆按鈕的接線非常簡單,一邊接地,另一方接 GPIO。在網上了解過後,我決定用較多人用的 BCM 作為 GPIO 的編號格式。同時選用了位於接口群右下方的 #20 及 #21 號腳;地線也選用了上兩格的 #18 號腳。它們位於外殻邊沿,能減少阻礙或鬆脫而導致接觸不良。焊接工作很快地完成了。正常來說,按鍵線路需要加入「上拉」或「下拉」設計,我曾向朋友 Peter 請教,了解兩者的分別及用法。主要都是用來確保未按鍵時的值,就像軟件中的 Initialise 一樣。然而,Raspberry Pi 已經內鍵了這兩種設計,GPIO.setup() 就是決定針腳是「上拉」或「下拉」。

    我測試了兩款在 Raspberry Pi 的 Python 中的按鍵檢測方法。第一種 GPIO.wait_for_edge() 是等候按鍵時會一直停著,直到狀態成立才能繼續,套用到 #21 號腳;第二種 GPIO.add_event_detect() 是事件方式,當按鍵發生時會直接跳到已登記的程式,套用到 #20 號腳。
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  AMIGO Camera Button Test
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import RPi.GPIO as GPIO
    import time
    
    ##------------------------------------------------------------
    def buttonPressed(channel):
        print("Button #20 pressed...")
    
    ##------------------------------------------------------------
    ##  Setup pins
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(20, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(21, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    
    GPIO.add_event_detect(20, GPIO.RISING, callback=buttonPressed, bouncetime=1000)
    
    ##------------------------------------------------------------
    while True:
        time.sleep(1)
    
        ##  Pressed
        GPIO.wait_for_edge(21, GPIO.FALLING)
        print("Button #21 pressed...")
    
        ##  Released
        GPIO.wait_for_edge(21, GPIO.RISING)
    
    ##------------------------------------------------------------
    GPIO.remove_event_detect(20)
    GPIO.cleanup()

    基本上兩種方法都能檢測到按鍵,兩種方法都有噪訊問題。但第二種方法因為提供了 bouncetime 設定,使得噪訊問題能較為容易解決;同時又不會使到其他程序停住,所以我決定選用 GPIO.add_event_detect 方法。

    2015年4月8日 星期三

    Python 計數程式

    Raspberry Pi 在啟動後日期會變成上一次關機時間。如果能上網的話,時間會自行跟現實世界同步。我把 Raspberry Pi 製成一台紅外線相機,在拍攝時預計沒有上網能力,那麼拍出來的相片要如何命名?參考了 Canon 的方法,以計數器作為 .JPG 檔名。我的做法是把計數器的值儲存到檔案內,於是編寫了以下的測試程式:
    #!/usr/bin/python
    ##------------------------------------------------------------
    ##  AMIGO Camera Counter Test
    ##  Copyright Pacess Studio, 2015.  All rights reserved.
    ##------------------------------------------------------------
    
    import os.path
    
    counter = 1
    filename = "counter.txt"
    
    ##  Check if file exists
    isFile = os.path.isfile(filename)
    if isFile:
    
        ##  Read file content
        with open(filename) as file:
            counter = int(file.read())+1
            print counter
    
    ##  Update counter to file
    file = open(filename, "wb")
    file.write(str(counter))
    file.close()

    每當執行一次程式,程式會把 counter.txt 內的值加一,並把最新的值儲存到檔案內。如 counter.txt 還沒有建立時,則會自行建立。

    2015年4月7日 星期二

    Raspberry Pi 紅外線相機盒子


    雖然 3D 打印問題有待解決,然而人手壓平方案算是過得去,於是繼續製作盒子中間的部份。經過幾次打印及調校,尺寸及位置都能滿足到需要;還欠頂蓋便能完成紅外線相機的外觀。下一步便是軟件開發及設定。

    2015年4月6日 星期一

    3D 打印中的浮起問題


    在 Inventor 畫了紅外線相機的底蓋,打算列印出來,可是膠料在打印的過程中浮起上來,甚至是移位問題。以前的打印間中試過這樣的情況,通常第二天再試便沒有問題,可能是氣溫及濕度的問題。不過,今次的問題卻一直存在。原本以為平台上的縐紋膠紙老化,無法版膠料抓著平台;更換過整個平台的膠紙,情況依舊。唯有想想其他可能性。想到了可能是膠料問題,於是換來 MakiBOX 跟機的啡色 PLA,同樣情況依舊。問過 3D 打印專家們,一位指要把縐紋膠紙改為 3M 的藍色打印用膠紙;而另一位則說是膠料受潮。兩者都要購買新的東西才能印證結果;暫時唯有在打印第一層時,人手把浮起來的膠料壓平...。

    2015年4月5日 星期日

    Raspberry Pi 的連接點設定


    新機體打算用 Raspberry Pi 2 作為主板,考慮到現今 iOS 及 Android 大行其道,機體需要支援這些流動裝置。但要開發 iOS 及 Android 控制程式的話十分費時,而且使用時需要安裝應用程式,若系統加入新功能後又需要升級,不太方便。所以,今次嘗試把控制程式安裝在 Raspberry Pi 內,透過 WiFi 連接點(Access Point)把 Pi 及手機連線,同時解決了平台、安裝及更新問題。

    不過問題來了。之前購買的 D-Link DWA-131 並不支援連接點模式,需要購買新的 WiFi USB。在淘寶找到了一些聲稱支援 Raspberry Pi 及連接點模式的 WiFi USB,價錢連運費約 HK$100。朋友替我在深水埗找到了一顆 TP-Link TL-WN725N,價錢只是 HK$85,二話不說便買了回來。參考了朋友的網頁進行設定。

    把 TL-WN725N 插進 Raspberry Pi 2,同時也接上 LAN 線、鍵盤、滑鼠。啟動 Raspberry Pi 後以 Pi 登入並輸入 ifconfig 會發現找不到 TL-WN725N。解決方法是:
    pi@raspberrypi ~ $ sudo wget https://github.com/lwfinger/rtl8188eu/raw/c83976d1dfb4793893158461430261562b3a5bf0/rtl8188eufw.bin -O /lib/firmware/rtlwifi/rtl8188eufw.bin

    重啟後再輸入 ifconfig 便能看到 TL-WN725N
    pi@raspberrypi ~ $ ifconfig
    eth0      Link encap:Ethernet  HWaddr 88:22:ee:cc:dd:ff  
              inet addr:192.168.1.10  Bcast:192.168.1.255  Mask:255.255.255.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:159 errors:0 dropped:0 overruns:0 frame:0
              TX packets:81 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:13895 (13.5 KiB)  TX bytes:12282 (11.9 KiB)
    
    lo        Link encap:Local Loopback  
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0 
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    wlan0     Link encap:Ethernet  HWaddr 33:bb:22:11:99:00  
              UP BROADCAST MULTICAST  MTU:1500  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

    之後確保系統在最新狀態:
    pi@raspberrypi ~ $ sudo apt-get update
    pi@raspberrypi ~ $ sudo apt-get upgrade

    接著是安裝 hostapd:
    pi@raspberrypi ~ $ sudo apt-get install hostapd isc-dhcp-server

    設定 DHCP 服務器,紅色是改動了的地方:
    pi@raspberrypi ~ $ sudo nano /etc/dhcp/dhcpd.conf
        ::    ::    ::    ::    ::
    # option definitions common to all supported networks...
    #option domain-name "example.org";
    #option domain-name-servers ns1.example.org, ns2.example.org;
        ::    ::    ::    ::    ::
    # If this DHCP server is the official DHCP server for the local
    # network, the authoritative directive should be uncommented.
    authoritative;
        ::    ::    ::    ::    ::
    subnet 192.168.2.0 netmask 255.255.255.0  {
            range 192.168.2.200 192.168.2.250;
            option broadcast-address 192.168.2.255;
            option routers 192.168.2.1;
            default-lease-time 600;
            max-lease-time 7200;
            option domain-name "rpi2-ap";
            option domain-name-servers 8.8.8.8, 8.8.4.4;
    }

    修改 wlan0 設定,紅色是改動了的地方:
    pi@raspberrypi ~ $ sudo nano /etc/network/interfaces
    auto lo
    
    iface lo inet loopback
    iface eth0 inet dhcp
    
    ##----------------------------------------
    ##  Original wlan0 settings
    ##----------------------------------------
    #allow-hotplug wlan0
    #iface wlan0 inet manual
    #wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
    #iface default inet dhcp
    
    allow-hotplug wlan0
    iface wlan0 inet static
      address 192.168.2.1
      netmask 255.255.255.0

    更換 hostapd:
    pi@raspberrypi ~ $ wget http://www.daveconroy.com/wp3/wp-content/uploads/2013/07/hostapd.zip
    pi@raspberrypi ~ $ unzip hostapd.zip
    pi@raspberrypi ~ $ sudo mv /usr/sbin/hostapd /usr/sbin/hostapd.bak
    pi@raspberrypi ~ $ sudo mv hostapd /usr/sbin/hostapd
    pi@raspberrypi ~ $ sudo chmod 755 /usr/sbin/hostapd
    pi@raspberrypi ~ $ sudo update-rc.d hostapd enable

    設定 Raspberry Pi 2 的 IP 地址:
    pi@raspberrypi ~ $ sudo ifconfig wlan0 192.168.2.1 
    pi@raspberrypi /etc/network $ ifconfig
    eth0      Link encap:Ethernet  HWaddr 88:22:ee:cc:dd:ff
              inet addr:192.168.1.120  Bcast:192.168.1.255  Mask:255.255.255.0
              UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
              RX packets:8958 errors:0 dropped:0 overruns:0 frame:0
              TX packets:3610 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:8855945 (8.4 MiB)  TX bytes:418480 (408.6 KiB)
    
    lo        Link encap:Local Loopback  
              inet addr:127.0.0.1  Mask:255.0.0.0
              UP LOOPBACK RUNNING  MTU:65536  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:0 
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)
    
    wlan0     Link encap:Ethernet  HWaddr 33:bb:22:11:99:00  
              inet addr:192.168.2.1  Bcast:192.168.2.255  Mask:255.255.255.0
              UP BROADCAST MULTICAST  MTU:1500  Metric:1
              RX packets:0 errors:0 dropped:0 overruns:0 frame:0
              TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
              collisions:0 txqueuelen:1000 
              RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

    設定 hostapd.conf:
    pi@raspberrypi ~ $ sudo nano /etc/hostapd/hostapd.conf
    interface=wlan0
    driver=rtl871xdrv
    ssid=RPi_AP
    hw_mode=g
    channel=6
    macaddr_acl=0
    auth_algs=1
    ignore_broadcast_ssid=0
    wpa=2
    wpa_passphrase=password   
    wpa_key_mgmt=WPA-PSK
    wpa_pairwise=TKIP
    rsn_pairwise=CCMP

    修改 hostapd.conf:
    pi@raspberrypi ~ $ sudo nano /etc/default/hostapd
    # Defaults for hostapd initscript
    #
    # See /usr/share/doc/hostapd/README.Debian for information about alternative
    # methods of managing hostapd.
    #
    # Uncomment and set DAEMON_CONF to the absolute path of a hostapd configuration
    # file and hostapd will be started during system boot. An example configuration
    # file can be found at /usr/share/doc/hostapd/examples/hostapd.conf.gz
    #
    #DAEMON_CONF=""
    DAEMON_CONF="/etc/hostapd/hostapd.conf"
    
    # Additional daemon options to be appended to hostapd command:-
    #       -d   show more debug messages (-dd for even more)
    #       -K   include key data in debug messages
    #       -t   include timestamps in some debug messages
    #
    # Note that -B (daemon mode) and -P (pidfile) options are automatically
    # configured by the init.d script and must not be added to DAEMON_OPTS.
    #
    #DAEMON_OPTS=""

    最後就是安裝 Nginx。成功後利用 iPad 接上 Raspberry Pi 2,開啟瀏覽器,輸入地址 http://192.168.2.1/,看到「Welcome Nginx!」便代表成功!

    2015年4月4日 星期六

    Inverse Kinematics


    自從第三屆《Hong Kong Mini Maker Faire》完結之後,Maker Club 都沒再有活動。得到新相識的網友 Samuel 的小孩身高機械人的觸發,今晚在 Brian 的工場舉行了一次小型聚會,主要是介紹機械人步行時的 Inverse Kinematics 技巧。目前《Tri-Robot》是以靜步行方式處理,我也很希望加入 Inverse Kinematics 操作。得到 Brian 的介紹,在構思有了初步的方向。其實除了二足步行機械人外,為機械臂加入 Inverse Kinematics 運算亦是一個十分好玩的題目,有機會一定要試試。

    2015年4月3日 星期五

    Raspberry Pi 鏡頭比較・二

    昨天試過室內的比較,今早試試日光室外的比較。


    我選了一棵大樹,有點背光,看看效果如何。左面的是普通鏡頭,拍出來的效果跟肉眼看的差不多;右面的是 NoIR 鏡頭,沒有加入藍色濾片,明顯地樹葉的綠色變成了淺黃綠色,而且樹上的大樹菠蘿的陰影位消失了。意味著是拍進了紅外線。


    接著是受光的場景,我選了一批灌木。普通鏡頭拍出來的色溫帶綠,跟肉眼看的有出入;NoIR 鏡頭拍出來的樹葉也變成了淺黃啡色,大廈兩種牆身顏色跟肉眼看的差不多,但有出入。


    再來一個遠景。普通鏡頭拍出來的色溫變回正常,跟肉眼看的差不多;NoIR 鏡頭拍出來的樹葉也變成了淺黃綠色。


    就 NoIR 鏡頭,我還嘗試了濾片效果。左面的是沒加濾片,亦即是說跟上面三個測試所拍攝的一樣;而右方則加了藍色濾片,像是把紅色光也過濾了,看上去感覺也較好。

    2015年4月2日 星期四

    Raspberry Pi 鏡頭比較


    朋友打算買 Raspberry Pi 2 及鏡頭,知道我手上有一顆 NoIR 鏡頭,問了我一些意見。他最想了解的是 NoIR 鏡頭在沒有加上藍色濾片時拍出來的效果。早前我已經試過,但當時背光及只拍了一張相片,加上內容色彩不多,而且是室內,難以判斷 NoIR 鏡頭在正常情況下的拍攝效果。所以特意在公司找個窗邊拍攝一次。上圖中樹木的綠色明顯不同,其他地方也有變色,但變化不大。

    我還拍攝了數張室內的相片:



    證明了 NoIR 鏡頭的加濾鏡是有點效用。不過,跟官網其他人加了濾鏡拍出來的效果有大的出入。我用的 NoIR 在沒加濾鏡的情況下似乎跟正常鏡頭一樣。為了印證 IR 及 NoIR 鏡頭的分別。朋友決定買顆普通 Raspberry Pi 鏡頭,希望我分別用普通鏡頭及 NoIR 鏡頭拍些照片做一個對比。


    左面綠色基板的是普通鏡頭;右面黑色基板的是 NoIR 鏡頭。


    普通鏡頭接的是 Raspberry Pi;而 NoIR 鏡頭接的是 Raspberry Pi 2。


    不知道是 Raspberry Pi 的問題,還是接線的問題;普通鏡頭拍出來的相片帶點訊號問題。


    看上去,沒加濾片的 NoIR 鏡頭拍出來的色溫效好。明天試試交換鏡頭,看看拍出來的效果會否一樣。

    2015年4月1日 星期三

    在 Google Spreadsheet 憑編號資料讀取另一表格的內容

    昨天前天分別講解了如何利用 Google Spreadsheet, Google Apps Script 及 Google Form 去處理活動資料,希望能減輕到《陳僖儀慈善基金》幹事們的工作量。我個人還想再進一步,直接按義工填寫的數據提取其個人資料。活動報名數據及義工資料已放在一起,很容易可以做到以上工作。以下是原本由 Google Form 傳回來的資料:


    我在數據的後方新增三行資料,並以橙色代表。


    以下是義工資料:


    為了方便幹事聯絡跟進,首先在 D 行驗證所輸入之電郵跟登記的電郵是否相同。相同的會標示「Yes」;不同的會標示「No」
    =IF (C2 = VLOOKUP(B2, 'Member List'!$A$3:$E$7, 3, FALSE), "Yes", "No")

    在 E 行讀取「Member List」表格中的義工名稱
    =VLOOKUP(B2, 'Member List'!$A$3:$E$7, 2, FALSE)

    在 F 行讀取「Member List」表格中的電話號碼
    =VLOOKUP(B2, 'Member List'!$A$3:$E$7, 4, FALSE)