2014年8月31日 星期日

創富媒體 03:人才泡沫


文章刊登於「創富媒體

公司成立了三年半,有一個經驗最為深刻;那就是招聘編程人員;是一件十分吃力耗時費心的事情。在招聘網頁刊登招聘廣告,付費的試過,免費的也有很多。基本上頭三天才會收到應徵者來信。不幸地,十個當中只有一個較為理想。而這個理想卻是在合格線的邊緣。我有問過自己的要求是否太高?要求只有一個「掌握一個熟悉的編程語言」。已經不是要求 Swift、Objective-C、Java 這些開發流動應用程式的語言。

試過兩次找到合適或有能力的應徵者,我本著「想清楚」、「看看還有沒有更好」的想法,考慮了一個星期;可能時間太久,他已經被另一家公司以更高的工資聘請了。兩位應徵者都給我「是這個人」的感覺,錯失了機會。自此以後,每當這個感覺再次出現時,都會在當天給予應徵者一個回覆。有的甚至給他要求更高的薪酬。這個方法有點改善,招聘工作算是達到了。

請人難這個問題,同事是感覺得到。他們一直都有留意市場的變化,因此造就另一個問題發生。程式員要求加薪。幅度達 25%~50%。公司要發展,總不能那麼請到一個,這邊走一個。培養及磨合的時間也是一種成本。在能力許可下當然不是問題,在營運得差的情況下,只要有資金,還是迫於無奈要加薪。有時在想,時下的程式員真的很幸福,沒甚麼能力的也能掙到很不錯的人工。就人事的問題,我曾經向行家請益,發現他們都有相同的情況。以我們這些初創事業,如果同事要求的薪金過高,只好不阻人發達。

不過,由於客戶的要求越來越高、功能也越來越多;平台也起碼 iOS 及 Android。程式員的要求亦開始嚴格起來。一是精通 iOS 或 Android 平台開發,一是需要熟悉兩者,以及後台的開發工作;亦即是服務器端。以前渾水摸魚時代快將過去。程式員要繼續享有優厚待遇的同時,自身的能力也要一併提升。能夠把行業內的人才泡沫沖走,是我非常期待的事情。

2014年8月26日 星期二

手勢操控


為了解決在 iPad 畫面上操控機體時,因缺乏觸感而無法隨心所欲的問題而思考良久;得出用手勢操控的方式。這就是新增的操作介面。使用者在畫面中間以雙指向右推,便會做出「Motion 1」動作;向上移,便會做出「Motion 2」動作;如此類推。這樣,駕駛員便不用望著屏幕來點選動作。只設定四個動作,是因為動作太多時會導致角度範圍收窄而出現誤差。在編程完成後會進行測試,要是四個動作不足以應付所需時,可加入三指,甚至四指操作。到時便會有 12 組動作,足以滿足需求。

2014年8月25日 星期一

AMIGO Controller 2.00 圖示設計


跟兩位拍檔討論後,《AMIGO Controller 2.00》將會獨立上架,讓有需要的《Tri-Robot》客戶可以隨時還原《AMIGO Controller 1.xx》。為了讓應用通過蘋果公司的審批,我決定更換應用圖示。上圖便是最新的設計,目前還沒有定案,希望在上架前能有更多的選擇。

2014年8月24日 星期日

打印 AMIGO Timelapse 外殻


渴望已久的 3D 打印機到手後,嘗試過打印不同的家務小部件,是時候升級了。今次打印的是《AMIGO Timelapse》的外殻。第一次打印失敗後,重新打印一次,效果不錯。頂蓋及底盒能順利接合,有一點點鬆動。當中使用了 0.5mm 公差值。相信要是 0.0mm 的話會合不上。


盒內收藏的是 Arduino Nano 及 BLE 模組,還有一個耳機插座,是用來連接《佳能》相機的快門接口。


盒子關上後,左方圓孔是接相機,右面梯形孔是接外置電源。


這就是安裝到相機後的感覺。看上去很老土,需要再設計一下外殻。


從前面看過去感覺好一點。之前設計過一個直條形狀,效果會好一點,不過打印起來較為麻煩。而且用 PLA 打印出來的支架不易移除。希望還能想到更輕巧細小的設計。

2014年8月23日 星期六

3D 打印心得


這幾天都有打印不同的部件,不過每次都出了狀況。如圖中的是《AMIGO Timelapse》的頂蓋。在外出吃早餐前打印得還很好,回來後卻發現 Y 軸被平均地分成三個階段移位了。向高手請教後,指有可能是 .gcode 碼出錯了,叫我把 .stl 再次輸出成 .gcode 後打印。結果十分順利地打印出來。

有一次打印時,發現 ArrayZ 的打印頭在空轉,沒有塑料被擠出。檢查過後,原來是塑料團下一圈卡著上一圈。打了一個死結。鬆一鬆開雖然可以能暫時解決問題,但只是把打結的時間往後伸延。最好是把塑料取下來,把打結的地方解開,再重新上料。不過,每團塑料的上線情況都不一樣,往後還有可能會出現打結的情況。需要小心留意。

不同廠商推出的塑料團的成份都有所不同,這樣會易響打印質素。ArrayZ 預設的 PLA 打印溫度為 190 度。但像我手上從 MakiBox 買入的 PLA 塑料團,在 190 度打印時會出現溶化的跡象,調校到 180 度的話,打印效果會較為理想。

有些模型的形狀不適合直接下印,需要加上支架來輔助。如用 PLA 塑料的話,支架是很難被移去,有時甚至會因此而弄破模型。建議還是使用 ABS 為妙。

2014年8月22日 星期五

解決 iOS 7 的漆黑啟動畫面問題


公司接了幾單項目,所有同事都忙著工作,唯有我也落場參與。這是一個 iPhone 項目,我最熟悉的平台。可是無論我怎樣設定,啟動畫面還是漆黑一遍。檢查過 Default.png, Default@2x.png, Default-568h.png 已加入了專案內,也檢查過 Images.xcassets 內的 Contents.json 設定,全部都正正常常,不知道為何啟動畫面依然漆黑。最終發現要把「iOS 7.0 and Later」中的「iPhone Portrait」取消打勾才行。真是奇怪...。

2014年8月21日 星期四

創富媒體 02:陳舊


文章刊登於「創富媒體

最近不約而同發生了兩件差不多的事件,都是跟編程有關。

第一件是客人提供了 CMS 的代碼參考。看過後,不論是代碼的編寫方法或是 CMS 網頁的設計外觀,都是上一個世紀的古老方法。程式沒有結構、雜亂無章,改起上來很容易出現問題;後台畫面簡陋,功能過少,數據也不好管理,需要逐個逐個輸入。實在接受不到時下還有這樣的 CMS 系統。我不禁問問客人:「是否因為預算所限而有這樣的結果?」。要是他回覆「是」的話,我倒也明白回報跟有幾多時間設計,多少時開發是成正比的;可是,答案卻是「我們一直用開,沒有問題呢!」。我沒有說這個方法不行,那個方法不好;只是很驚訝而矣。但可以肯定開發者沒有心。有心做,就算錢少,都會做得好。

第二件是新來了一位編程同事,由於有 iOS 開發經驗,我給他研究過去的項目;得到的回應是:「公司用的方法很陳舊,還用 MySQL?應該用 CoreData。」、「我三年前已經用 Interface Builder」、「還用自己寫?連網應該用 AFNetworking。」...等等等等。今次的位置對調了,被新同事指手畫腳說「陳舊」。對此,我沒有甚麼感覺,只認為要快及 Work 就行了;而且有些考慮是經驗累積下來,我想他是沒有碰過。當然,我也支持使用新方法,但要考慮交貨時間。時間到了,交不到貨,就算你將來寫到識飛又如何?未起飛,已死了。對於很有衝勁的年輕人來說,給他機會,可能成就更好的未來。

今次放手一搏,希望他的承諾能夠兌現!也希望證明我的方法是陳舊,但思想絕不陳舊。

2014年8月20日 星期三

Beats Studio


很久之前,在 Sita 陳僖儀的 Facebook 專頁看到她戴著 beats 耳機聽歌,心想是不是音色很好呢?對於不多聽音樂,亦不懂聽音樂的我,是一無所知。只知 beats 幾千港元一隻,很貴!不過,我卻想擁有相同的體驗。


在封閉的 Sita 群組內問過其他歌迷,得知她這對耳筒是 beats by dr.dre。走訪過百老匯、豐澤、蘇寧、New vision 都找不到這個型號。市面上的都是 2.0 產品了。唯有到美國 Amazon 找找,發現還有少量這個型號的產品,而且降價到 US$171.57。查看 www.price.com.hk 找到最平賣 HK$2,200。連運費總數 US$190.03,兌換成港紙約為 HK$1,600,比香港的還要便宜 HK$600 大元。思考了兩天決定訂購。原本要 8 月 29 日到達,今天卻來了。試機當然要找 Sita 的歌曲。戴後感覺音質比 iPhone 耳機好,低音有點力量,但不及想像中好。包裝也沒有 Apple 產品般能憑直覺輕易打開。整體來說,不值得這個價錢。要不是為了 Sita 而買,我想還只會停留在「想買」的階段吧。

2014年8月19日 星期二

SteelSeries Stratus Gamepad


工作需要,買來兩台支援 iOS 的手掣。精緻的手掣價錢一點都不精緻,盛惠 HK$778。還好,是客人付。

這顆手掣是藍牙 2.1 的產品,是 MFi 的產物。亦即是說接駁的方法跟支援藍牙 4.0 的《Tri-Robot》不同;需要預先配對好才能使用。利用 GameController Framework 能夠讀取按鍵情況。可是在開發應用時,發現這顆手掣的嚴重問題。按鍵的內容沒有跟 Framework 的指示。如按了 Y 鍵傳回的是 B 值、按了 B 卻傳回 A 值、按了 R1 竟然傳回 L1 值;甚至連十字掣的左鍵也無法檢測。幸好客人的程式只用上了三顆按鍵,而且只是示範之用,只要自行配對好便能解決問題...。

2014年8月18日 星期一

Desk Balancer・二


昨天帶了 ArrayZ 回家後一直都忙著,到深夜才安頓好它,沒有時間嘗試打印。其實在 Chris 的工作室嘗試過打印細小的物件,所以要成功打印應該沒有問題。今日做完家務,照顧好女兒睡覺後,拿出半年有多以前畫好的 Desktop Balancer 小試牛刀。模型是在 Inventor 製作 .stl 檔,然後經 Mac 版本的 KISSlicer 計算打印路徑,並生成 .gcode 檔。把 G-Code 檔放入 SD 卡,利用 ArrayZ 打印。結果非常順利,成功把模型打印出來。

2014年8月17日 星期日

ArrayZ 到著


經過 MakiBox 慘痛的教訓之後,原本不打算購買沒有品牌的 3D 打印機。但認識了香港 3D 打印教父 Chris 及他的作品 ArrayZ 之後,受到他的熱誠與質素影響,我改變了想法。最終還向 Chris 購入一台 ArrayZ。今天,終於把 ArrayZ 帶回家。香港的家庭普遍都較為細小,原本打算把它放在飯桌下面,但清潔地板時會少不免會產生碰撞,還是放棄了。臨急臨忙要找另一個位置,容得下 40cm x 50cm x 50cm 體積的談何容易。幸運地,經過調配安排,找到了一個能容得下 ArrayZ 的空間...。

2014年8月15日 星期五

搬移模式的使用方法


又花了一個晚上,完成了搬移模式的畫面效果;至於順序還未能真正儲存,有待開發製作。影片在模擬器擷取下來,較真機跑得慢一點、卡一點。但能表達出使用方法,總算不錯吧!

2014年8月14日 星期四

搬移模式


一直用開《AMIGO Controller》的朋友會遇到一個問題,就是當動作資料越來越多時,便會想整理一下。最常見的是把攻擊動作放在一起,把移動的動作放在一起。可是目前並沒有支援像 iOS 桌面的搬移模式。用家需要在建立第一個動作時做好規劃,這是多麼有遠見的舉動。因此,我打算加入搬移模式。

這個過程比想像中麻煩,花了一晚間才弄得出比較像樣的震動效果。接著要處理拖拉時的移動效果及實質的位置順序。起初沒有想過有這樣的要求,還沒有頭緒怎樣才做得簡潔流暢...。另外一個問題是,究竟如何進入搬移模式好呢?為此想到了兩個方法。第一個方法是在畫面最下方加一顆編輯按鈕。這樣做會縮短放鍵的面積,畫面也不好看;若展開控制器時會遮擋著編輯按鈕。第二個方法是長按「動作制禦」啟動搬移模式。反正這個按鈕在此時此刻是沒有作用,而且不用加多一顆按鈕,滿足到我追求簡單的方向。同時再長按便能關閉搬移模式。我個人十分喜歡這個設計。

2014年8月13日 星期三

PDF Master Key


找到一份關於 PDF 的文件說明了加密工作是如何鍊成。基於我的理解,嘗試從這個方向入手,希望能更快找出密碼。不過,還是失敗了。解密工作始終不是我杯茶...。

以下內容節錄自 https://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
The following explains how PDF encryption, using Adobe's "Standard
Security Handler", works.  This is what you get if you select document
security options in PDF 1.3 (Acrobat 4.x) or earlier.  All of this
information is in the publicly available PDF spec:

    http://partners.adobe.com/asn/developer/technotes/acrobatpdf.html

The goal of protecting a PDF file is typically something like this:
you want a viewer application to be able to display the file but not
be able to print it.  (PDF has additional options to disallow
copy-and-pasting text and editing the file; the same argument applies
to them.)  The problem here is that exactly the same information is
used for both functions -- once you have a (decrypted) page
description, it can be turned into either pixels on the screen or
toner dots on a printed page.

Adobe's PDF protection scheme is a classic example of security
throughd obscurity.  They encrypt the content of a PDF file and hope
that no one figures out how to decrypt it.  When Adobe's viewer
encounters an encrypted PDF file, it checks a set of flags, and allows
certain operations (typically viewing) while disabling others
(typically printing).

Now PDF is supposedly an open standard, and in fact, Adobe has been
pretty good about documenting it.  They initially refused to release
detailed information on the encryption; presumably they were aware of
the security/obscurity issues.  But they eventually relented.  Various
third-party PDF viewers have been able to display encrypted PDF files
for a few years now.  (And then there was the issue of exporting
crypto code from the US, but that's a different story, and not Adobe's
fault.)

Before explaining how PDF file encryption works, let me give some
background on PDF files.  A PDF file consists of a series of objects,
each identified by two numbers (object number and generation number).
There is also a cross reference table which maps object numbers to
their positions in the file.  There are several object types.  The
important ones here are:

    dictionary: a table that maps names to objects (like a Perl hash)

    stream: an arbitrary chunk of data; streams are used for font
            files, page descriptions (much like a simplified
            PostScript prorgram), image data, etc.

Every document has a "trailer dictionary" which holds references to a
few important things (like the tree of page objects which contains the
document content) and optionally to an encryption dictionary.  If the
encryption dictionary is present (i.e., if the document is encrypted),
it contains the information needed to decrypt the document.  An
example:

    % Trailer dictionary
    trailer
    <<
        /Size 95         % number of objects in the file
        /Root 93 0 R     % the page tree is object ID (93,0)
        /Encrypt 94 0 R  % the encryption dict is object ID (94,0)
        /ID [<1cf5...>]  % an arbitrary file identifier
    >>

    % Encryption dictionary
    94 0 obj
    <<
        /Filter /Standard   % use the standard security handler
        /V 1                % algorithm 1
        /R 2                % revision 2
        /U (xxx...xxx)      % hashed user password (32 bytes)
        /O (xxx...xxx)      % hashed owner password (32 bytes)
        /P 65472            % flags specifying the allowed operations
    >>
    endobj

There are two passwords: "user" and "owner".  Typically, the user
password is not set (i.e., set to the empty string), thus allowing
anyone to view the file.  If a PDF file is loaded into Adobe Acrobat,
and the user supplies the owner password, all operations are allowed
(including re-encrypting the PDF file with different passwords, etc.).
Remember that neither password is actually used in the encryption --
the viewer application is simply expected to check that the user
supplied the password before allowing him/her to print (etc.).

In the above example, the permission flags are 65472 (decimal) or
1111111111000000 (binary).  Bits 0 and 1 are reserved (always 0), bit
2 is the print permission (0 here, meaning that printing is not
allowed), and bits 3, 4, and 5, are the "modify", "copy text", and
"add/edit annotations" permissions (all disallowed in this example).
The higher bits are reserved.

The encryption key is generated as follows:

    1. Pad the user password out to 32 bytes, using a hardcoded
       32-byte string:
           28 BF 4E 5E 4E 75 8A 41 64 00 4E 56 FF FA 01 08
           2E 2E 00 B6 D0 68 3E 80 2F 0C A9 FE 64 53 69 7A
       If the user password is null, just use the entire padding
       string.  (I.e., concatenate the user password and the padding
       string and take the first 32 bytes.)

    2. Append the hashed owner password (the /O entry above).

    3. Append the permissions (the /P entry), treated as a four-byte
       integer, LSB first.

    4. Append the file identifier (the /ID entry from the trailer
       dictionary).  This is an arbitrary string of bytes; Adobe
       recommends that it be generated by MD5 hashing various pieces
       of information about the document.

    5. MD5 hash this string; the first 5 bytes of output are the
       encryption key.  (This is a 40-bit key, presumably to meet US
       export regulations.)

Note that the inputs to this algorithm are: the user password
(typically empty) and various information specified in the PDF file.

The hashed user password (/U entry) is simply the 32-byte padding
string above, encrypted with RC4, using the 5-byte file key.
Compliant PDF viewers will check the password given by the user (by
attempting to decrypt the /U entry using the file key, and comparing
it against the padding string) and allow or refuse certain operations
based on the permission settings.

The hashed owner password is also generated using MD5 and RC4.
Details are given in the PDF 1.3 manual, but it's completely
irrelevant if you have the user password (or the user password is
null).

All stream (and string) objects in the PDF file are encrypted.  This
is sufficient to render the file useless (that is, if it weren't so
easy to decrypt).  Stream/string decryption works like this:

    1. Take the 5-byte file key (from above).

    2. Append the 3 low-order bytes (LSB first) of the object number
       for the stream/string object being decrypted.

    3. Append the 2 low-order bytes (LSB first) of the generation
       number.

    4. MD5 hash that 10-byte string.

    5. Use the first 10 bytes of the output as an RC4 key to decrypt
       the stream or string.  (This apparently still meets the US
       export regulations because it's a 40-bit key with an additional
       40-bit "salt".)

To decrypt a PDF file (i.e., generate a new PDF file, identical except
that all encryption is removed), just filter the file, applying the
above algorithm to decrypt every stream and string object.  Then
remove the /Encrypt entry in the trailer dictionary.

If you have the user password or the user password is null (or you
have the owner password), you can decrypt the PDF file.  The only time
when there is some protection is when both the user and owner
passwords are set; in this case you'd be stuck doing a brute force
attack on 40-bit RC4.

PDF 1.4 (Acrobat 5.x) includes a revised security handler.  I have
only briefly skimmed the PDF 1.4 spec, but it appears to add three
things:

    * algorithm 2, which is identical to algorithm 1 (above) except
      that it allows longer keys

    * algorithm 3, which is unpublished (which the PDF 1.4 spec claims
      is "an export requirement of the U.S. Department of Commerce".)

    * revision 3 (which applies to algorithms 1-3), which adds some
      steps (runing MD5 and RC4 multiple times), presumably to slow
      down brute force attacks

There are also third-party encryption plugins, which are, of course,
not publicly documented.  The ElcomSoft presentation points out that
some of these are even easier to break than Adobe's encryption.

2014年8月11日 星期一

兆基解密


得到一個加了密碼的 PDF,但沒有密碼。要如何打開呢?暫時知道密碼組合是 0-9 及 a-f,32 個字符,256-bits。以前試過用 Windows 平台的密碼搜尋工具,那時運行了三天也沒有變穫;知道 CoreGraphics 有 PDF 相關的功能,沒試過,於是嘗試一下。簡單編了個 Mac 程式,感覺會快一點。跑了一天,嘗試了 37,000,000 個組合,距離最大上限還有非常之遠的長度,應該要跑一年才能找到,還是另尋他法...。
#import "AppDelegate.h"

@implementation AppDelegate

int crackPDF(const char *filename)  {
 CFStringRef path = CFStringCreateWithCString (NULL, filename, kCFStringEncodingUTF8);
 CFURLRef url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, 0);
 CGPDFDocumentRef myDocument = CGPDFDocumentCreateWithURL(url);

 if (myDocument == NULL) {
  printf ("can't open `%s'.", filename);
  CFRelease (url);
  return EXIT_FAILURE;
 }
 CFRelease (url);

 if (CGPDFDocumentIsEncrypted(myDocument)) {

  const char *startPassword = "00000000000000000000000000000000";
  size_t length = strlen(startPassword);
  char password[length+1];

  //  Set initial password
  password[length] = 0;
  for (int k=0; k<length; k++)  {
   password[k] = startPassword[k];
  }
  
  printf("\nDecode start...");
  int i = 0;
  int count = 0;
  bool done = false;
  while (done == false)  {

   done = CGPDFDocumentUnlockWithPassword(myDocument, password);
   if (done == false)  {

    size_t k = length-1;
    while (k > 0)  {

     password[k]++;
     if (password[k] == ':')  {password[k] = 'a';}
     if (password[k] >= 'g')  {
      password[k] = '0';
      k--;
     }  else  {
      k = 0;
     }
    }
   }  else  {
    printf("\nUnlocked by %s", password);
   }

   i++;
   if (i >= pow(16, 32))  {
    done = true;
    printf("\nLoop limit reached.\n\n");
   }

   count++;
   if (count >= 100000)  {
    count = 0;
    printf("\n100000 done...%s", password);
   }
  }
 }

 if (!CGPDFDocumentIsUnlocked (myDocument)) {
  printf("can't unlock `%s'.", filename);
  CGPDFDocumentRelease(myDocument);
  return EXIT_FAILURE;
 }

 size_t pageCount = CGPDFDocumentGetNumberOfPages(myDocument);
 printf("\nPage count: %ld", pageCount);
 if (pageCount == 0) {
  CGPDFDocumentRelease(myDocument);
  return EXIT_FAILURE;
 }

 return EXIT_SUCCESS;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification  {
 int result = crackPDF("sita_chan.pdf");
 NSLog(@"Done!");
}

@end

2014年8月8日 星期五

工人姐姐


最近實在有太多生離死別。姨仔患了癌症、朋友的爸爸徐尚田大師傅過身、另一朋友的爸爸搞的生命歡送會、工人姐姐今日滿約離開。一直認為三年多前開公司是第一次做老闆;想清楚,其實八年前已經成為僱主。那是為大女兒聘請的工人姐姐。八年裡聘請兩位工人姐姐,兩位都做了四年。現在沒有工人,感覺上是脫離了僱主的範疇。督信術數的我,碰巧八年這個數字,更覺得是十年的僱主運快將完結,進入下一個十年運。

我不喜歡生離死別。內子說:「難道有人喜歡嗎?」。我想了一想,實在沒有人喜歡。當刻有一剎那頓悟的感覺。說回工人姐姐,她就像我公司裡的員工一樣,在完美主義眼光下的人,都是做得不夠好、都是有很多地方做錯、都是有很多時間在偷懶。一想到這樣,就不由得想她快快完約離開。可是,當時刻來臨時,就很想時間回流。工人姐姐如此,公司內的同事如是。我想是因為怕改變吧。無論怎樣,他們都是照顧過或幫助過我的人。不過,時間不會等人。我亦因此而特別著重時間。時間是唯一不能逆轉的東西。信我,這是我花了很多時間思索得出來的結果。至少以我的慧根是這樣的結果。可能就如《超能煞姬》中露西所講,萬物是沒有意義,唯有在時間流轉下才變得存在。希望工人姐姐日後的生活過得好。

2014年8月7日 星期四

機體改名功能


放下了《AMIGO Controller》有一段時間,是時候給力給力,完成多一點進度,好讓我向同伴有個交代。

七月時到澳門參觀 RBL 比賽時留意到一個問題。在場有超過十台《Tri-Robot》,全部機體都是使用 RedBear 的 BLE mini。因為 RedBear 的藍牙沒有改名功能,所以同學們在連線時,會發現有很多相同名字的藍牙。加上藍牙不穩定,導致 UUID 不是時常都能顯示。因此,同學們都要大聲疾呼,呼籲大家不要把《Tri-Robot》開著又不連接,好讓自己能順利連線。

看到這樣的場面,實有羞愧。我怎可能容忍自己的產品有這樣的問題發生;於是著手修改《AMIGO Controller》,加入了為機體改名的功能。但需要藍牙能提供到 UUID 才可。雖然不是完美的解決方案,但都算能改善一下。希望日後更換藍牙模組後,以往要大家斷線讓路的情況不要再出現!

2014年8月6日 星期三

PHP 儲存檔案突破 /var/www/html

三年前,由於 NAS 已不勝負荷,於是買了一台 PC 並安裝 CentOS 作為網頁服務器。當時對 Linux 的知識有限,不知道原來 /var 及 /home 是在不同的硬碟區間,導致今天浮現出空間不足的問題。

一直以來都認為 2TB 的硬碟空間十分足夠,三年來不同項目才用了接近 1TB,理應還能支持多三年的活動。可是,昨天發現 MySQL 出了狀況,使用 phpMyAdmin 也無法登入。腦內完全沒有概念,不知道如何修理,唯有嘗試重啟。當然重啟解決不了問題。之前已經知道區間的問題存在,但仍有 10GB 空間,新項目亦是放在 /home 內,盡量減低空間的消耗,以不影響日常的運作空間。於是靈機一觸,嘗試清理一下不用的檔案,便發現 MySQL 能夠回復正常。幸好這個方法能解決問題,不然一定搞得很久。

能把 10GB 燃燒到盡,是因為新同事把兩個項目都放在 /var 內,而這兩個項目每日都會有大量的圖片上傳到服務器。為解決燃眉之急,於是把這些圖片都搬到 /home 內。繼而把 PHP 程式內的圖片目錄試圖改到 /home 之內。可是卻出現了問題:


原來 PHP 是無法把檔案儲存在 /var/www/html/,亦即是網頁服務器根目錄之外。經過一輪嘗試之後,找到解決方法:

1. 把原本的儲存目錄移走或刪除

2. 在 PHP 程式的目錄加入新的儲存目錄的捷徑,並以原來名稱命名
    ln -s /home/www/html/project_xxx/signatures/ signatures

3. 為新的儲存目錄加入權限 777

4. 最重要是執行以下命令
    chcon -R -t httpd_sys_content_t /home/www/html/project_xxx/signatures/

這樣就能把圖檔由 /var 改為儲存在 /home 了。

2014年8月5日 星期二

創富媒體 01:數字會說謊

文章刊登於「創富媒體

作為流動應用開發者,特別是要開發自家產品的組織來說,都很留意手機市場的最新情況。其中最常考慮的就是開發在哪個手機平台?iOS 還是 Android?就這個問題,我跟拍檔都有不同的想法。

從商業的角度出發,很自然會選擇市場大的平台,同一個佔有率在不同大小的市場會有著數十倍百倍甚至千倍的利潤。因此手機的賣出量便成為一個指標、平台的使用者數目便成為一個準則。我的拍檔就是這麼想。這亦往往是各大廠商售賣產品時推銷的數字。不過,這樣會很容易跌入一個盲點。我認為最重要是「活躍使用者人數」。手機賣得多、平台人數多,不等如真正用的人多。要是選了一個人多的平台,但大部份人都不常用的話,那市場就會比想像中細。

其中一例子就是當初三星推出第一部四核心電話時一樣。有朋友跟筆者說「你看今次這部 Android 很快,一定快過 iPhone。」。單單從數字「四核」跟「兩核」比較是對的;但若果了解 Android 及 iOS 在結構上的分別,便會知道 Android 介面要做得像 iPhone 流暢,是需要花多倍的資源才能夠達成。

很多朋友看見我有很多蘋果產品、說蘋果產品是最好時,便都認定我是「果迷」,而沒有看看我背後花了多少功夫去研究、去證明、去思考,亦沒有人想想我說的是否有理。就像看到表面的數字便下結論一樣,往往會看不到背後隱藏著的真實數字。如果抱著「數字會說話」的觀念來看待事物,而沒有獨立思考的話,就有可能會作出錯誤的評估。

2014年8月4日 星期一

紅米 + Arduino Nano


《AMIGO Camera》的 Android 部份寫了基本的大崗,打算接上 Arduino Nano 進行測試。測試是這樣的,用瀏覽器連接 Android 上 HTTP 服務器,點擊網頁內的按鈕會發放訊號到 Arduino Nano。Arduino Nano 上的 LED 會由明變暗,或暗變明。這是順利的話能夠得到的結果。

事情當然不會這樣順利。鍵按過了,LED 燈卻沒有反應。翻查代碼找不到古怪的地方,於是焦點落在硬件上;硬件的接線很簡單,又似乎沒有問題;於是聚焦在 OTG 的串口上。找來免費的《USB Host Diagnostics》一試,原來偵測不到 Arduino,難怪沒有反應。最後發現紅米 OTG 輸出的電壓不夠,需另外加接電源,便能成功偵測到 Arduino Nano...。

2014年8月3日 星期日

創富媒體

7 月 7 日,行家 Raymond 告訴我,指他們推出了一個名叫《創富媒體》的資訊平台,打算集各方所長,一起增值。我認為這個概念很好。他也邀請我一起撰文分享。起初以為只是隨口說說而已,除了技術方面我有點心得之外,其他沒有甚麼可以教導別人。

7 月 28 日,Raymond 再次向我查詢想法。為了支持這個好想法,我決定身體力行。同時,我也希望能發展自己的寫作及思考能力,嘗試撰寫非技術方面的文章,好好學習一下。

8 月 2 日,我終於完成第一篇撰文。很吃力地想出題目,但表達得不太好,希望大家能領會到我的說話,亦希望寫作能力及表達能力得到提升,寫出有質素有意思的文章。多謝 Raymond 給予我一次增值的機會。