2022年2月28日 星期一

[開箱] Google Pixel Buds A-Series

心癢了買了一個新藍牙耳機 XD 一開始是看到 Google TW 在蝦皮的特價活動,隨手查了一下,原價 3290 元,在非 Google 官方的賣場大型網購平台可以 2500 左右購得(有些低方還低到 2200左右),就買啦!原本就一直想買一款當備品。

大概去年被朋友推坑買了 魔浪mifo O7 藍牙耳機,用起來都還滿方便的,特別是體積小這件事,很隨身攜帶。唯一的困擾是個人不小心按久按耳機會觸動 Siri (像是拿下來跟人對話,不小心就久按到),然後在 Siri 模式下,最常就是播打電話出去!雖然一年不會觸動個 3~5 次,但啟動還是很擾人。

這次體驗 Google Pixel Buds A-Series + iOS 手機,整體上無法把 Google 耳機的強項整合一起(Google Now) ,倒反而讓我覺得很不錯,整體上就是無線耳機+按一下暫停/播放等模式,也不怕喚起 Siri ... XD 此外,購買之前就看到一些評價說容易聽到環境音(有點像先天故意設計?),我反而滿愛這種像耳掛式耳機的感受,因為我最常聽 Podcast 等類,不需要極安靜環境,反而最怕跟環境完全隔離。

只能說,此時的我,對大家給他的負評價都剛好很能接受(甚至變成我喜愛的點),若真的要說缺點的話,大概是 iOS 搭配使用 Google Pixel Buds A-Series 時,不能靠觸碰耳機調整聲音大小,查手冊才發現是要靠音控控制。

2022年2月15日 星期二

[PHP] 使用 Built-in web server 和 Router Script 開發嵌入式產品的網頁介面

話說幾年前也寫過一篇筆記 [PHP] 使用 PHP built-in web server 及 PHP CodeIgniter framework 。現在則是使用的場景不同了,在筆記一下使用的情境。

不少嵌入式產品的 UI 是採用 Web 實作的,近幾年則是趨向於 App UI 。然而開發 embedded linux 產品時,其 Web UI 也是打包到 embedded linux 裡頭,這時要開發測時,就滿不方便。若只是單純改改裡頭 js code 則可以免強靠 Browser 做 JS Inject 來達成。

目前協助擴展嵌入式產品的 Web UI 維護團隊,過去曾嘗試把裡頭的 Web UI 使用 Vue.js 跟 webpack 打包機制,弄出個 webpack 自起 Web Server 並透過 devServer.proxy 把 CGI request 導向到實體 device ip。目前因為沒有啟用 Vue.js 所以也沒啟用 webpack 等機制,暫時就先用簡單的 PHP 內置Web Server 機制

$ php -S localhost:8000 -t PATH_DOCUMENT_ROOT

然而,在 fw build code 流程中,可能會有多處檔案搬移的設計,以 Web 來說,大概可以硬搞成 /js/main.js 其實擺在 /tmp/path1/js 中,而 /css/main.css 擺在 /tmp/path2/css 等等,這時就要靠 使用路由(Router)脚本 來處理

$ cat routing.php
<?php

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

if (!strncmp($URL_PATH, '/js/', 4)) {
$file_path = '/tmp/path1/js' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}

if (!strncmp($URL_PATH, '/css/', 4)) {
$file_path = '/tmp/path2/css' . $URL_PATH;
if (file_exists($file_path)) {
echo file_get_contents($file_path);
return true;
}
}
return false;

使用:

$ php -S localhost:8000 -t /tmp routing.php

如此,若有一包 fw code 時,可透過 routing 機制指定某些檔案要從哪裡取得(例如需要 patch 的檔案等),因而避開 build code 後才能測試,便方便許多。另外,還須多補寫一下 /cgi-bin/ 的部分,在把 request 改發到對應的裝置上,如此在面對不需改動 CGI 問題時,可以輕鬆測試 Web UI。

另外,該 routing.php 也可以很結構化:

$ cat routing.php
<?php

function log_info($message) {
$stderr = fopen('php://stderr', 'w');
fprintf($stderr, '=> '.$message."\n");
fclose($stderr);
}
function tree_files( $input_file ) {
if (!file_exists($input_file))
return array();

if (is_dir($input_file)) {
$output = array();
if ($handle = opendir($input_file)) {
while (false !== ($file = readdir($handle))) {
if ($file == '.' || $file == '..')
continue;
$path = $input_file . '/' . $file;
if (!file_exists($path))
continue;
if (is_dir($path)) {
$sub_output = tree_files( $path );
foreach($sub_output as $f) {
array_push($output, $f);
}
} else {
array_push($output, $path);
}
}
closedir($handle);
}
return $output;
}
return array( $input_file );
}
$request_handler = array(
'routing' => array(
'file' => array(
'/index.html' => '/tmp/study/haha.html',
'/world.html' => '/tmp/study/hello/',
),
'dir' => array(
'/js/' => '/tmp/study',
'/css/' => '/tmp/study',
),
),
'content_type' => array(
'html' => 'text/html',
'htm' => 'text/html',
'css' => 'text/css',
'js' => 'text/javascript',
'svg' => 'image/svg+xml',
),
);

$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$path_parts = pathinfo($URL_PATH);

log_info('routing-init, URL_PATH:['.$URL_PATH.'], REQUEST_URI:['.$_SERVER['REQUEST_URI'].']');
log_info('in routing-file mode');
foreach($request_handler['routing']['file'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-file mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = is_dir($target) ? $target . $URL_PATH : $target;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-file, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}
log_info('in routing-dir mode');
foreach($request_handler['routing']['dir'] as $pattern => $target ) {
if (!strncmp($URL_PATH, $pattern, strlen($pattern))) {
log_info('in routing-dir mode: match pattern: ['.$pattern.'], target: ['.$target.']');
$path = $target.$URL_PATH;
if (file_exists($path)) {
$path_parts = pathinfo($URL_PATH);
$content_type = isset($request_handler['content_type'][$path_parts['extension']]) ? $request_handler['content_type'][$path_parts['extension']]: mime_content_type($path);

header('Content-Type: ' . $content_type);
$size = filesize($path);
header('Content-Length: '.$size);
echo file_get_contents($path);
log_info('routing-dir, response: content-type:['.$content_type.'], content-length:['.$size.'], path:['.$path.']');
return true;
}
}
}

return false;

如此一來,在 /tmp/study 又建立一個檔案結構:

% tree /tmp/study 
/tmp/study
├── css
│   └── main.css
├── haha.html
├── hello
│   └── world.html
└── js
    └── main.js

3 directories, 4 files

使用 php -S localhost:8000 routing.php 時,就會一些目錄切換的對應機制。

2022年2月9日 星期三

[開箱] UVC Video Capture Card / 視頻采集卡 / 影像擷取卡 - HAGiBiS


繼上次買了不到500元的 UVC Video Capture Card 測試 小米4K 電視棒,這次換價格高一點的 Hagibis 海備思 Type-C 影像擷取卡,他多了 Type-C 跟 USB-A 轉接頭,使用上應當會方便不少。看了下核心的硬體規格,都是輸出最高是 1080p 30Hz 的影像格式。

實際測試 Chromecast 2018 版,使用 VLC 或 QuickTime Player 的確也正常可以取得,雖然我覺得這件事應該算異常?三年前試過圓剛 4500 元左右(現在跌到不用1500元)等級的影像擷取盒,則是會阻擋下來:Chromecast 與 AVerMedia ExtremeCap UVC BU110


看來小廠的 UVC Video Capture Card 可能在 DRM 放行?!此例用 NETFLIX 當作例子。雖說用 Browser 看 NETFLIX 本來也有類似螢幕錄影的機制,這時只能感謝 macOS / Safari 在 DRM 這塊做得很出色,連截圖都是黑壓壓的。

由於工作上要處理很多帶有 HDMI 的設備,未來就靠這 UVC 影像擷取卡來幫忙省螢幕,還可以搭配 Python OpenCV 做影像處理,像是自動化設備測試、影像分析服務等等。