2016年3月30日 星期三

OpenCV 3.1 + iOS SDK 9 效果測試


最近學到的 OpenCV 3.1 知識,沒想過這麼快便遇到應用的機會。

工作上打算製作新的影像類應用程式。我不想新瓶舊酒,老是拍照後加文字加貼紙,想加入新的元素,於是動手製作一個 OpenCV 3.1 + iOS SDK 9 的示範程式來測試想法能否順暢地實現。由於是影像類應用程式,人臉偵測實在少不免。不過目前無法偵測斜了角度的面,側面也同樣偵測不到;而且偵測到的尺寸時大時小,還有很多需要改善的空間。

2016年3月29日 星期二

甚麼時候需要找 App Developer?

接觸客戶的機會多了,也嘗試在他們的角度去看事情,有一個深刻的想法;究竟一家公司甚麼時候需要找 App 開發公司替自己編寫應用程式?我的想法是:

如果應用程式是為一個活動而設,可以找 App Developer 開發;
如果應用程式是人有我有的話,可以找 App Developer 開發;
但如果一直營運下去的話,最終還是要自行組隊。

一直經營的應用程式,一定會有很多想改善的地方;找 App 開發公司就會為每一個改動計算價錢,加起來的費用可能還是比請一位全職開發人員低,但要得到最終效果,往往花很多時間在溝通上;萬一其中一方有一點遺漏,事情可能已經發展到一半,就算肯改也花了時間。還要對方不抬高價錢,否則只能肉隨砧板上,或是另覓方法。

有時你肯付費修改,遇上人手緊絀的開發公司,也未必能以最快速度進行開發。若遇上判上判的開發公司,需時更久。還要有好的眼光,選到一家不會倒的開發公司。不然,連源代碼都沒有,到時重頭來過談何容易。你能等,你的客人可以嗎?就算付費連源代碼也買下又如何?無人能確保源代碼能繼續在一日千里的 App 世界中暢通無阻。就像一個三年前的項目,當時用 Eclipse 開發,到今日 Eclipse 已經不再被 Google 支援,大家通通改用 Android Studio。問題是要把 Eclipse 專案搬到 Android Studio 是很痛苦,得轉用支援 Android Studio 的第三方程式庫。Unity 也有同樣的情況。我看 iOS 也有一點危機。Apple 力推 Swift,是否意味著有朝一日會淘汰 Objective-C?到時轉會又要花一筆金錢。金錢事少,時間事大。一直經營的應用程式,功能只會越來越多,到重頭來過,花的時間一定不少。老闆們一定要有預算。

2016年3月25日 星期五

Clickbot 機械設計


應承了朋友開發《Clickbot》已有一段時間,一直未能想好它的機械結構。既然有四天假期,是履行承諾的時候。反覆構思,終於想到較為像樣的機械設計。原本想立即打印,但剛修理好的 ArrayZ 原來還沒有把問題解決,仍然會在打印途中,推料馬達前後移動,無法正常輸出物料,造成打印失敗。又要得花時間理解問題發生的原因,之後才能打印...。

2016年3月24日 星期四

ArrayZ 再一次固障


最近很多倒霉的事情,3D 打印機就是其中之一。在打印的過程中,擠料的馬達會前後前後轉動,一出一入的動作變成沒有原料被擠出,打印基本上是空轉。這台 ArrayZ 不是第一次出事,相同的情況也曾發生;當時只要按一按底板相關的插座便能解決,今次同樣方法已經失效。問題估計是接觸不良引起。既然之前的方法只能短期有效,唯有重新製作接線,解決長遠的問題。

家裡有備用零件,把插頭、電線、針腳焊接好後,為 ArrayZ 進行搭橋手術,順利完成。開機試試擠出,成功了。於是找一個模型打印,今次在最最最開頭的量度熱板水平時出了狀況。探針能順利伸了出來,卻在着地時沒有彈回!哎,解決了一個問題,又引伸出另一個問題!可惡!已經沒有心機處理,只好在 .gcode 著手,把探針的部份移除...。

2016年3月23日 星期三

原來參數可以這樣傳

跟另一家公司合作去競投項目,需要開發一個簡單的示範程式。我負責手機部份,對方負責服務器接口。

為了爭取時間盡快完成,可以拿去跟潛在客戶展示。我跟對方的編程同事夾了相關接口需要的參數,以及傳回的內容。格式用方便的 JSON。程式編好後,接口也到着,可是放在一起後發現無法連接。我對網絡方面的知識不多,看到網址的格式是「https://www.sitachan.com:310/」感到有點其怪。我認識的 HTTPS 是會導入到 443 埠,若地址帶有 310 埠,最終會是 443 還是 310 呢?如果是 443 則沒有懸念,若是 310 的話,數據會否被 SSL 加密呢?時間有限,我沒有深究。反正只是示範,日後再作跟進。對方改為 HTTP 搞掂。

之後引伸出另一個問題。無論我怎樣依照參數名稱及格式,都無法傳回內容。這裡也有一個奇怪點:參數也是 JSON 格式。
var parameter = JSON.stringify({
    "login":{
        "uuid":"A0B1C2D3E4F5G6H7",
        "timeStamp":"2016-03-23 12:34:56",
        "deviceType":"iPhone SE"
    }
});

$.ajax({url:"https://www.sitachan.com:310/",
    type: "POST",
    data: parameter,
    success: function(responseData)  {
        console.log(responseData);
    },
    error: function(request, status, error)  {
        console.log(request.responseText+": "+error);
    }
});
於是我編寫了一個 PHP 程式以相同參數、相同格式去讀取數值,用來確定手機程式沒有出錯。結果在我方的服務器運行得很順利;但一傳給對方的服務器卻讀不出任何數據...。讓我相信問題出在這裡,於是向對方索取他在服務器端讀取參數的代碼:
$json = json_decode(file_get_contents("php://input"), true);
很奇怪。我接觸到的不是 $_GET["login"]、$_POST["login"] 就提 $_REQUEST["login"],沒見過是 file_get_contents("php://input")。難怪 PHP 會讀不進任何東西、難怪對方說參數沒有名稱,不是「Key - Value」的方式...。有趣!

搞清楚後,焦點返回示範程式向服務器的請求方法。於是修改 PHP 程式來測試手機是否傳回對方能讀到的內容:
//========================================================================================
$jsonString = file_get_contents("php://input");
$json = json_decode($jsonString, true);
echo("{}");
error_log("jsonString: $jsonString");

foreach (getallheaders() as $name => $value) {
    error_log("$name: $value");
}
示範程式在 iPhone 上執行,用 AFNetworking 來連接服務器。可是怎麼搞都必須是「Key - Value」配對方式。反覆測試後,發現需要用「AFHTTPSessionManager」而不是用開的「AFHTTPRequestOperationManager」:
NSString *urlString = @"https://www.sitachan.com:310/";
NSDictionary *parameterDictionary = @{@"login":[NSDictionary dictionary]};

//--------------------------------------------------------------------------------------------------
//  Send it out to server
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
[manager POST:urlString parameters:parameterDictionary success:^(NSURLSessionDataTask *task, id responseObject)  {
 
    //--------------------------------------------------------------------------------------------------
    //  Success
    NSString *jsonString = [[NSString alloc] initWithData:responseObject encoding:NSUTF8StringEncoding];
    PSLog(@"Response: %@ connection done!\n%@", APPMANAGER_API_QUEUESTATUS, jsonString);

    NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableContainers error:nil];

    NSMutableDictionary *itemDictionary = [[NSMutableDictionary alloc] init];
    NSArray *dataArray = [dictionary objectForKey:@"data"];
    for (NSDictionary *dataDictionary in dataArray)  {

        NSString *itemNumber = [dataDictionary objectForKey:@"item"];
        NSString *key = [itemNumber substringToIndex:3];

        [itemDictionary setObject:itemNumber forKey:key];
    }
    _itemDictionary = itemDictionary;

    _errorCount = 0;

    [[NSNotificationCenter defaultCenter] postNotificationName:@"loginDone" object:nil userInfo:nil];
}  failure:^(NSURLSessionDataTask *task, NSError *error)  {

    _errorCount++;

    //--------------------------------------------------------------------------------------------------
    //  Failed, but never mind the result, just for record
    PSLog(@"### %@ %@", urlString, [error localizedDescription]);
    NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:error.localizedDescription, APPMANAGER_KEY_ERROR, nil];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"connectionError" object:nil userInfo:dictionary];
}];
原來參數可以這樣傳,又上了一課!

2016年3月22日 星期二

人生的下半場

回顧創業五年,發現有很多可以做得再好的地方。問題是做得更好就代表適合當老闆嗎?近幾個月,我不斷思索這個問題。

我嚮往穩定的生活,平衡的生活。王維基說過,要 Life-Work Balance 的生活就不應當老闆。我認為,這是因為王維基做不到,不等如其他人做不到。不過,我認同他的說法。每個人一日都只有 24 小時,人生是需要取捨。在術數的哲學中,平衡代表著平庸。所以要好的生活,某些東西是需要捨棄。不想捨棄的話,就不能要求更高。這是物理世界的定律;等價交換的道理。

除此之外,做老闆需要有眼光,我沒有;做老闆需要有膽色,我沒有;做老闆需要敢冒險,我沒有;做老闆需要有領導才能,我沒有;做老闆需說謊,我不願。五年來的經營不善,更加覺得自己不適合。好了,來到人生的下半場,要出來打工當然可以,但我更希望自主自立,時間由自己安排。意味著自僱是我的最佳職業。不過,要如何確保月入能支持家庭的開支?我一直都找不到答案。畢竟何時有生意不是由我控制;我可以左右,但成事是天時、地理、人和。如果能剋服這個心理障礙,我便能跳出框框。

2016年3月21日 星期一

no lapack/blas resources found

在 Udacity 上報讀了「Intro to Machine Learning」有一段時間都沒有認真學習,終於的起心肝觀看教學影片。當中需要在自己的電腦上進行 Python 編程。可是在安裝過程出現了一連串問題。

先是在 MacBook Pro 上安裝,但 Python 2.7 及 Python 3.5 設定得不好,弄得 site-packages 互相干擾。於是找來一台 CentOS 服務器安裝,又因為 PIP 程式升級後出現 Locale 問題。後來在 VirtualBox 的 CentOS 中進行設定,又遇上「sklearn」封包無法安裝。我在 CentOS 上的 Python 版本為 2.6,能對應課程內的程式。而「sklearn」封包是需要「scipy」封包,「scipy」封包又要「numpy」封包。於是先安裝「numpy」。完成後卻出現「numpy.distutils.system_info.NotFoundError: no lapack/blas resources found」。解決方法是「yum install blas-devel lapack-devel」安裝以上兩個封包。這樣就能順利安裝「scipy」封包。

2016年3月20日 星期日

OpenCV3: 檢測移動物件


今日嘗試了用 OpenCV3 檢測移動物件,同時把影像、變化、二值化、灰階層錄影到影片檔。程序比以較長但不算複雜。
##------------------------------------------------------------------------------
##  Motion detection with OpenCV 3.1
##------------------------------------------------------------------------------
##  Language: Python 3.5
##  Platform: Mac OS X
##  Written by Pacess HO
##  Copyright 2016 Pacess Studio.  All rights reserved
##------------------------------------------------------------------------------
 
##  Import packages
import imutils
import numpy
import time
import cv2

##  Reading from webcam
camera = cv2.VideoCapture(0)
time.sleep(0.25)

##  Initialize the first frame in the video stream
firstFrame = None

fourcc = cv2.VideoWriter_fourcc(*"MJPG")
writer = None
zeros = None
width = 512
height = 288

##------------------------------------------------------------------------------
##  Forever loop until user quit the application
while True:

   ##  Grab the current frame and initialize the occupied/unoccupied text
   (grabbed, frame) = camera.read()

   ##  If the frame could not be grabbed, then we have reached the end of the video
   if not grabbed:
      break

   ##  Resize the frame, convert it to grayscale, and blur it
   frame = imutils.resize(frame, width)
   gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
   gray = cv2.GaussianBlur(gray, (21, 21), 0)

   ##  If the first frame is None, initialize it
   if firstFrame is None:
      firstFrame = gray
      continue

   ##------------------------------------------------------------------------------
   ##  Compute the absolute difference between the current frame and first frame
   frameDelta = cv2.absdiff(firstFrame, gray)
   thresh = cv2.threshold(frameDelta, 25, 255, cv2.THRESH_BINARY)[1]

   ##  Dilate the thresholded image to fill in holes, then find contours on thresholded image
   thresh = cv2.dilate(thresh, None, iterations=2)
   (_, contourArray, _) = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

   ##------------------------------------------------------------------------------
   ##  Loop over the contours
   for contour in contourArray:

      ##  If the contour is too small, ignore it
      if cv2.contourArea(contour) < 500:
         continue

      ##  Compute the bounding box for the contour, draw it on the frame, and update the text
      (x, y, w, h) = cv2.boundingRect(contour)
      cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)

   ##------------------------------------------------------------------------------
   ##  Show the frame and record if the user presses a key
   cv2.imshow("Security Feed", frame)
   cv2.imshow("Thresh", thresh)
   cv2.imshow("Frame Delta", frameDelta)

   ##------------------------------------------------------------------------------
   ##  Write images to video file
   if writer is None:

      ##  Initialzie the video writer and construct the zeros array
      writer = cv2.VideoWriter("output.avi", fourcc, 20, (width*2, height*2), True)
      zeros = numpy.zeros((height, width), dtype="uint8")

   ##  Convert single channel to RGB channel
   FRAMEDELTA = cv2.merge([frameDelta, frameDelta, frameDelta])
   THRESH = cv2.merge([thresh, thresh, thresh])
   GRAY = cv2.merge([gray, gray, gray])

   ##  Construct the final output frame
   output = numpy.zeros((height*2, width*2, 3), dtype="uint8")
   output[0:height, 0:width] = GRAY
   output[0:height, width:width*2] = FRAMEDELTA
   output[height:height*2, 0:width] = THRESH
   output[height:height*2, width:width*2] = frame
 
   ##  Write the output frame to file
   writer.write(output)

   ##------------------------------------------------------------------------------
   ##  If the `q` key is pressed then exit
   key = cv2.waitKey(1) & 0xFF
   if key == ord("q"):
      break

##------------------------------------------------------------------------------
##  Cleanup the camera and close any open windows
writer.release()
camera.release()
cv2.destroyAllWindows()

2016年3月19日 星期六

OpenCV3: 人臉偵測


今日嘗試了人臉偵測。跟往常的影像分析一樣,都是先把相片轉換為灰階影像後再進行處理。因為 OpenCV 的緣故,代碼非常精簡。檢測人臉特徵的設定都在 XML 檔內。
##------------------------------------------------------------------------------
##  Face and eyes detection with OpenCV 3.1
##------------------------------------------------------------------------------
##  Language: Python 3.5
##  Platform: Mac OS X
##  Written by Pacess HO
##  Copyright 2016 Pacess Studio.  All rights reserved
##------------------------------------------------------------------------------

##  Import packages
import numpy as np
import cv2

##  Load image
image = cv2.imread("sita_chan_01.jpg")
 
##  Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

##  Display original image in a window
cv2.imshow("Original", image)

##------------------------------------------------------------------------------
##  Face and eyes detection
faceCascade = cv2.CascadeClassifier("/Users/pacess/opencv/data/haarcascades/haarcascade_frontalface_default.xml")
eyeCascade = cv2.CascadeClassifier("/Users/pacess/opencv/data/haarcascades/haarcascade_eye.xml")
faceArray = faceCascade.detectMultiScale(gray, 1.1, 5)
for (x, y, w, h) in faceArray:
    cv2.rectangle(image, (x, y), (x+w, y+h), (182, 89, 255), 2)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = image[y:y+h, x:x+w]

    eyeArray = eyeCascade.detectMultiScale(roi_gray)
    for (ex, ey, ew, eh) in eyeArray:
        radiusW = int(ew/2)
        radiusH = int(eh/2)
        radius = radiusW if radiusW < radiusH else radiusH
        cv2.circle(roi_color, (ex+radiusW, ey+radiusH), radius, (0, 192, 255), 2)

##------------------------------------------------------------------------------
##  Display output image
cv2.imshow("Output", image)

##  Wait any key pressed
cv2.waitKey(0)
cv2.destroyAllWindows()

2016年3月18日 星期五

與一勤老闆有約


今日約了《香港人撐香港品牌》中的一勤老闆 Jacky Chan 見面,請教一下做生意的心得。獲益良多。

席間我跟拍檔 Hugo 分享了 Not-Bag 過去半年來的事情,由生產、出貨、拍攝、到拆伙,讓 Jacky 了解我們更多,給予的意見更加貼地。最值得參考的是「數據化」創業模式。把生意產生出來的數據進行整理及分析,從而對生意作出調整。賣得好的產品賣多一點、賣得不好的產品賣少一點、賣得好的渠道賣多一點、賣得不好的渠道賣少一點、吸納到新客源的多做一點、趕走舊客源的少做一點、但廣告點都要長做一點。Jacky 以將軍來形容廣告。它不能令一家沒生意的公司立即起死回生,打廣告需要時間的投資,不是一時三刻就有回報的事情;還要看生意的類型。廣告是把種子埋在客人的心裡,當需求出現時便會想起。像 Not-Bag 的,一個人一年會買多少個袋?我問:「那是否沒有得做?」。「又不是沒有得做,要分兩條線:一條走平價、一條走質素。平價的讓人們知道 Not-Bag 這個品牌;質素的讓人知道 Not-Bag 的價值。但將會比較難打。」他說。

Not-Bag 還在模索出路,已找到一條大量定制生產的柱光,Jacky 更即場幫忙聯絡相關的朋友幫忙,希望這條路能越行越好。

2016年3月17日 星期四

在 El Capitan 安裝 OpenCV 3.1 及 Python 3.5

參考了 http://www.pyimagesearch.com/2015/06/29/install-opencv-3-0-and-python-3-4-on-osx/ 的指引進行 OpenCV 3 及 Python 3 的安裝。當中加入了自己修改的部份:

1. 到 App Store 安裝 Xcode
   我們會使用到它附帶的開發工具。

2. 安裝 Homebrew
   由於發現更新 Homebrew 失敗,所以先把 Homebrew 移除後再重新安裝。我們會用它來安裝 Python 3 及 OpenCV 用到的程式庫。
$ cd ~
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/uninstall)"
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update

3. 安裝 Python 3
   Mac OS X 跟機的是 Python 2.7。以下安裝的是 Python 3,並把 Python Launcher 3 及 IDLE 3 加到應用程式選單。
$ brew install python3
$ brew linkapps

   檢查 Python 3 路徑。如果輸出是「/usr/local/bin/python」代表安裝路徑正確。
$ which python3
/usr/local/bin/python

   檢查 Python 3 是否順利安裝。如果是「Python 3.x.x」代表成功。
$ python3 --version
Python 3.5.1

4. 升級 PIP
   我們需要用 PIP 來安裝不同的程式庫,所以先確保 PIP 是最新版本。
$ pip3 install --upgrade pip

5. 設定 Python 3.5 開發環境
   這樣做是把開發環境按項目分開,讓不同項目可使用不同版本的 Python。
$ pip3 install virtualenv virtualenvwrapper

   之後需要修改 .bash_profile 內容。
$ vi ~/.bash_profile

   按「i」後加入以下設定:
   ## Virtualenv/VirtualenvWrapper
   export VIRTUALENVWRAPPER_PYTHON=/usr/local/bin/python3
   source /usr/local/bin/virtualenvwrapper.sh

   按「Esc」及「:wq」儲存並離開。
$ source ~/.bash_profile

   接著是建立虛擬開發環境。
$ mkvirtualenv cv3 -p python3

之後如要使用建立了的虛擬開發環境,只要:
$ workon cv3

   安裝 Python 另一常用的程式庫 Numpy。
$ pip3 install numpy
$ pip3 install imutils

6. 安裝編譯 OpenCV 的所需軟件
$ brew install cmake pkg-config
$ brew install jpeg libpng libtiff openexr
$ brew install eigen tbb
$ brew install freetype

7. 編譯 OpenCV
$ git clone https://github.com/Itseez/opencv.git
$ git clone https://github.com/Itseez/opencv_contrib
$ cd ~/opencv
$ mkdir build
$ cd build

   留意 Python 3 也有不同的版本,下面使用的路徑需要配合本機的目錄。如果以下一項發生錯誤,在處理後需要由 cmake 來過。
$ cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D PYTHON3_PACKAGES_PATH=~/.virtualenvs/cv3/lib/python3.5/site-packages -D PYTHON3_LIBRARY=/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/lib/libpython3.5m.dylib -D PYTHON3_INCLUDE_DIR=/usr/local/Cellar/python3/3.5.1/Frameworks/Python.framework/Versions/3.5/include/python3.5m -D INSTALL_C_EXAMPLES=OFF -D INSTALL_PYTHON_EXAMPLES=ON -D BUILD_EXAMPLES=ON -D BUILD_opencv_python3=ON -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules ..
$ make -j4
$ sudo make install

8. 檢查 OpenCV 是否順利安裝
   最後,在 Python 3.5 內載入 OpenCV 3.1。
$ python3
   Python 3.5.1 (default, Mar 16 2016, 19:55:24)
   [GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
   Type "help", "copyright", "credits" or "license" for more information.
   >>> import cv2
   >>> cv2.__version__
   '3.1.0'
   >>>

2016年3月16日 星期三

OpenCV3: AKAZE


一直對 Computer Vision 懷有興趣,每隔一段時間都有機會接觸到要使用影像分析的項目。當年開發的投影打雀遊戲、商標辨認、Weather Kids 的白紙偵測...等。這些都是自行研發的技術,沒有使用第三方的程序庫。最近想以 Raspberry Pi 開發一個影像分析項目,不想再自行開發,改而使用著名的 OpenCV。於是先在 Mac 上體驗及使用一下。好不容易才安裝好 OpenCV,立即試試效果:
##------------------------------------------------------------------------------
##  Detect keypoints and extract AKAZE features with OpenCV 3.1
##------------------------------------------------------------------------------
##  Language: Python 2.7
##  Platform: Mac OS X
##  Written by Pacess HO
##  Copyright 2016 Pacess Studio.  All rights reserved
##------------------------------------------------------------------------------

##  Import packages
from __future__ import print_function
import cv2

##  Load image
image = cv2.imread("sita_01.jpg")

##  Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

##  Display original image in a window
cv2.imshow("Original", image)

##  Initialize the AKAZE descriptor, then detect keypoints and extract local invariant descriptors from the image
detector = cv2.AKAZE_create()
(kps, descs) = detector.detectAndCompute(gray, None)
print("keypoints: {}, descriptors: {}".format(len(kps), descs.shape))

##  Draw the keypoints and show the output image in another window
cv2.drawKeypoints(image, kps, image, (0, 255, 0))
cv2.imshow("Output", image)

##  Wait any key pressed
cv2.waitKey(0)
cv2.destroyAllWindows()

2016年3月15日 星期二

在 Mac OS X 安裝 Python 的 Freetype 程式庫

Pacess:~/matplotlib pacess$ brew install freetype
==> Downloading https://homebrew.bintray.com/bottles/freetype-2.6.3.el_capitan.bottle.tar.gz
######################################################################## 100.0%
==> Pouring freetype-2.6.3.el_capitan.bottle.tar.gz
🍺  /usr/local/Cellar/freetype/2.6.3: 60 files, 2.5M

2016年3月10日 星期四

陳僖儀 x Beat MP3


又到了 3 月 10 日陳僖儀生日的日子。Facebook 封閉群組《ALL THE BEST...SITA》內有不少粉絲懷念這位唱得的小天使;而我則選用了遊戲的方式去思念她。

2016年3月6日 星期日

植物棚支架


一直想把窗前沒用的晾衫空間變成花槽,今天有時間實行,於是量好空間尺寸,走到吉之島購買合適的部件。選了一個網格架。回家後,一放才發現網格架長了點,容不下。然而,把網格架提起也能做到原先想要的效果。畫好了部件圖,利用 3D 打印技術生產了兩個柱狀物來支撐網格架,以索帶連接,比原先的設計更加牢回。真是錯有錯著。

2016年3月5日 星期六

Raspberry Pi LCD


樹莓派的 LCD 顯示屏幕想買多年,新年有利是錢可用,價錢相宜,決定入手。


貨在周三到着,把玩了三天,仍然只得白色畫面一個。LCD 屏幕用不到。我原本的樹莓派安裝了 NOOBS 版本,按照說明書第六段關於在自定義系統的樹莓派上安裝的指示,仍然是白屏。網上有人說用 Raspbian 就能解決。到 raspberrypi.org 下載最新 2016-02-26 版 Raspbian Jessie,同樣不行。試了三次仍然不行。三次是使用不同方法,一次用 Lite 版,一次用完全版,一次用不同的驅動程式。甚至連 SD 卡也換張新的,通通不行。向淘寶賣家查詢,說只能用他們提供的鏡像系統,沒有其他方法。我當刻大怒!網頁沒說其他系統不行,簡直騙人!我不相信,既然他們能製造鏡像系統,那便有方法在自定系統上重做。找呀找,在網上找到一個非常詳細的教學。試畢,仍然不行...。


最終只好乖乖聽話,用賣家提供的 Raspbian Wheezy。成功了。無言...。

2016年3月4日 星期五

利用 Arduino IDE 開發 ESP8266


除了用 NodeMCU 開發板及韌體以 Lua 對 ESP8266 進行編程外,還可以用 C 語言。這就要使用 Arduino IDE 了。

設定相當簡單。執行 Arduino IDE 後,選單「Preferences...」,在「Additional Boards Manager URLs」欄輸入「http://arduino.esp8266.com/stable/package_esp8266com_index.json」及點擊「OK」


在「Tools」選單中點「Board」中的「Boards Manager...」。拉到最底會找到「esp8266 by ESP8266 Community」。點一下這個選項後再按「Install」進行安裝。


完成後再回到「Tools」選單中點「Board」,會看到「NodeMCU 1.0」。如果選了 NodeMCU 的話,在「File」選單中的「Examples...」才會出現 ESP8266 相關的範例。

2016年3月3日 星期四

ESPlorer


刷新 NodeMCU 韌體後,再次連接到 Mac OS X。雖然出現亂碼,但今次 ESPlorer 能正常連線。我利用 NodeMCU 開發板上自帶的兩顆 LED 來編寫第一個 Lua 程式。內容很簡單,先是定義兩顆 LED 的針腳編號及當前狀態:
boardPin = 0
chipPin = 4
flag = 0
設定兩針腳為輸出接口:
gpio.mode(boardPin, gpio.OUTPUT)
gpio.mode(chipPin, gpio.OUTPUT)
建立每半秒執行一次的計時器,及當中的開燈關燈邏輯:
tmr.alarm(0, 500, 1, function()
 flag = flag+1

    if (flag == 1) then
        gpio.write(chipPin, gpio.LOW)
        gpio.write(boardPin, gpio.LOW)
    end

    if (flag == 2) then
        gpio.write(chipPin, gpio.HIGH)
 end

 if (flag == 3) then
  gpio.write(chipPin, gpio.LOW)
        gpio.write(boardPin, gpio.HIGH)
 end

 if (flag == 4) then
        gpio.write(chipPin, gpio.HIGH)
        flag = 0
    end
end)
完成後,把程序上傳到 NodeMCU 並命名為「init.lua」。這樣每次開機時都會自動被執行。

2016年3月2日 星期三

在 Windows 8 刷新 NodeMCU 韌體


上星期在淘寶購買的 NodeMCU 開發板送到來了。接上電源後,板上兩顆藍色 LED 燈不停地閃動,表示運作正常。


正打算嘗試編寫 Lua 程序,卻發現在 Mac OS X 上使用的 ESPlorer 無法跟 NodeMCU 開發板連接,花了一點時間也找不到原因。於是決定從互聯網下載最新韌體,把它刷新到板上。不過,在 Mac 上遇到不少問題。像是 PIP 版本過舊、esptool 找不到 Serial 程序庫、安裝 Pyserial 卻出現 SSL 錯誤、之後又有「Cannot detect archive format」...等。唯有改到 Windows 上處理,日後再戰 Mac 的安裝。

轉到 Windows,首先是下載 NodeMCU Flasher 及韌體。韌體有兩個版本:Integer 及 Float。我選了 Integer 版本。接上 NodeMCU 後,Windows 8 能自動找到 USB 驅動程式,立即可用。連接埠為 COM4。


在「Config」頁的第一行選擇韌體檔案,後面的地址設定為「0x00000」。


在「Advance」頁的 Baudrate 選「115200」,燒錄大小選「4MByte」,燒錄速度選「40MHz」,SPI 模式選「DIO」。


回到「Operation」頁點擊「Flash」揭鈕開始燒錄。



完成後利用 LuaLoader 能順利連接 NodeMCU。這樣便可以用右面的控制板面直接操作 NodeMCU。

2016年3月1日 星期二

以 Arduino 製作 Samsung 紅外線遙控


早幾天買了紅外線 LED 回來,加上 Arduino Nano,足以製作 Samsung 紅外線遙控。線路非常簡單。只要一顆紅外線 LED 接 220 Ohm 電阻接三號腳,加 IRremote 程序庫,簡單的編程就可以。本來要逐個按鍵數值逐個找,卻發現網上已有有心人列出所有數值。編程用最簡單的讀取 UART 決定發射哪個訊號就行!
//----------------------------------------------------------------------------------------
//  Samsung IR Remote Version 1.00
//  Created by Pacess HO on 2016-Feb-29
//  Copyright Pacess HO, 2016.  All rights reserved.
//----------------------------------------------------------------------------------------

#include <IRremote.h>

//----------------------------------------------------------------------------------------
//  https://wiki.samygo.tv/index.php5/Infrared_receiver/transmitter_support
#define KEY_ARROW_DOWN    0xE0E08679
#define KEY_ARROW_LEFT    0xE0E0A659
#define KEY_ARROW_RIGHT   0xE0E046B9
#define KEY_ARROW_UP      0xE0E006F9
#define KEY_BLUE          0xE0E06897
#define KEY_CHANNEL_0     0xE0E08877
#define KEY_CHANNEL_1     0xE0E020DF
#define KEY_CHANNEL_2     0xE0E0A05F
#define KEY_CHANNEL_3     0xE0E0609F
#define KEY_CHANNEL_4     0xE0E010EF
#define KEY_CHANNEL_5     0xE0E0906F
#define KEY_CHANNEL_6     0xE0E050AF
#define KEY_CHANNEL_7     0xE0E030CF
#define KEY_CHANNEL_8     0xE0E0B04F
#define KEY_CHANNEL_9     0xE0E0708F
#define KEY_CHANNEL_DOWN  0xE0E008F7
#define KEY_CHANNEL_UP    0xE0E048B7
#define KEY_ENTER         0xE0E016E9
#define KEY_EXIT          0xE0E0B44B
#define KEY_FASTFORWARD   0xE0E012ED
#define KEY_GREEN         0xE0E028D7
#define KEY_INFO          0xE0E0F807
#define KEY_MENU          0xE0E058A7
#define KEY_MUTE          0xE0E0F00F
#define KEY_PAUSE         0xE0E052AD
#define KEY_PLAY          0xE0E0E21D
#define KEY_POWER         0xE0E040BF
#define KEY_RED           0xE0E036C9
#define KEY_RETURN        0xE0E01AE5
#define KEY_REWIND        0xE0E0A25D
#define KEY_SLEEP         0xE0E0C03F
#define KEY_SOURCE        0xE0E0807F
#define KEY_STOP          0xE0E0629D
#define KEY_VOLUME_DOWN   0xE0E0D02F
#define KEY_VOLUME_UP     0xE0E0E01F
#define KEY_YELLOW        0xE0E0A857

#define SAMSUNG_BITS      32

//----------------------------------------------------------------------------------------
IRsend irsend;

//----------------------------------------------------------------------------------------
void setup()  {
  pinMode(3, OUTPUT);

  Serial.begin(9600);
  Serial.println("--------------------------------------------------------");
  Serial.println("--  Samsung IR Remote v1.00                           --");
  Serial.println("--  Copyright Pacess HO, 2016.  All rights reserved.  --");
  Serial.println("--------------------------------------------------------");
}

//----------------------------------------------------------------------------------------
void loop()  {
  if (Serial.available() == 0)  {return;}

  int key = Serial.read();
  uint32_t keyCode = 0;
  switch (key)  {
    default:  return;

    case '*':  keyCode = KEY_POWER;  break;
    case '0':  keyCode = KEY_CHANNEL_0;  break;
    case '1':  keyCode = KEY_CHANNEL_1;  break;
    case '2':  keyCode = KEY_CHANNEL_2;  break;
    case '3':  keyCode = KEY_CHANNEL_3;  break;
    case '4':  keyCode = KEY_CHANNEL_4;  break;
    case '5':  keyCode = KEY_CHANNEL_5;  break;
    case '6':  keyCode = KEY_CHANNEL_6;  break;
    case '7':  keyCode = KEY_CHANNEL_7;  break;
    case '8':  keyCode = KEY_CHANNEL_8;  break;
    case '9':  keyCode = KEY_CHANNEL_9;  break;

    case 'w':  keyCode = KEY_ARROW_UP;  break;
    case 'x':  keyCode = KEY_ARROW_DOWN;  break;
    case 'a':  keyCode = KEY_ARROW_LEFT;  break;
    case 'd':  keyCode = KEY_ARROW_RIGHT;  break;
    case 's':  keyCode = KEY_ENTER;  break;

    case 'q':  keyCode = KEY_EXIT;  break;
    case 'e':  keyCode = KEY_MENU;  break;
    case 'z':  keyCode = KEY_SOURCE;  break;

    case '-':
    case '_':  keyCode = KEY_CHANNEL_DOWN;  break;
    case '=':
    case '+':  keyCode = KEY_CHANNEL_UP;  break;
    
    case '{':
    case '[':  keyCode = KEY_VOLUME_DOWN;  break;
    case '}':
    case ']':  keyCode = KEY_VOLUME_UP;  break;
    
    case 'r':  keyCode = KEY_RETURN;  break;
    case 't':  keyCode = KEY_STOP;  break;
    case 'y':  keyCode = KEY_PLAY;  break;
    case 'u':  keyCode = KEY_SLEEP;  break;
    case 'i':  keyCode = KEY_INFO;  break;
    case 'o':  keyCode = KEY_FASTFORWARD;  break;
    case 'p':  keyCode = KEY_MUTE;  break;
    case 'k':  keyCode = KEY_REWIND;  break;
    case 'l':  keyCode = KEY_PAUSE;  break;
    
    case 'f':  keyCode = KEY_RED;  break;
    case 'g':  keyCode = KEY_GREEN;  break;
    case 'h':  keyCode = KEY_YELLOW;  break;
    case 'j':  keyCode = KEY_BLUE;  break;
  }

  char buffer[64];
  sprintf(buffer, "Key [%d] pressed...", key);
  Serial.println(buffer);
  irsend.sendSAMSUNG(keyCode, SAMSUNG_BITS);
  delay(500);
}