imuslab
托比的實驗記錄部落格
Go build 突然變得很慢?
最近在 Windows 更新之後不知道為甚麼 go build 變得很慢,我在想會不會是因為我的 SSD 快要死了所以讀寫速度掉不少,於是有一天我終於忍不住去跑 Crystal Disk Mark 好像沒啥問題的樣子 所以我就在想,會不會是 Go compiler 的 cache 問題,於是用以下 command 把 go cache 的 folder 換到一只直插 PCIE 的 SSD 上面 > go env -w GOCACHE=E:\tmp\gocache > go env GOCACHE E:\tmp\gocache cache 有出現在快 SSD 上,所以是有改成功 結果還是很慢。而且 go compiler 的 CPU usage 也卡在 10% 左右,然後我就注意到,另外大約 70% 的 CPU usage 都是被其他東西吃掉的。被甚麼吃掉? 幹你的 Windows Security 幹你的又是 Windows Security。它對 Go compiler 產出來的每個 binary cache file 掃描,這樣不慢才怪。Windows 自 Win7 之後從來就沒好過,要不是我間中要打遊戲我一早就換去用 Linux 了。 所以知道原因之後要解決就很簡單,就是去 Windows Security > 管理 把即時保護設定關掉 然後 go build 就回復到正常 build 速度了。
香港人跑到台灣正顎遊記
背景故事 在中學(台:國中)的時候我的牙醫看到我的情況一直都在跟我說要做矯正,我都說不要,主要原因是我有看過倒及(台:戽斗 / 英:Underbite)這個基本上透過矯正基本上算於沒救(就算能矯正過來臉型還是很醜),再加上咬合導致的顎關節有聲或者痛這問題看來也很常見,那不如不要浪費錢還比較好。 就是這樣這個問題一直拖到大學,後來在快畢業的時候遇到疫情,一直載口罩加上因為嘴巴沒辦法完全閉合所以一直在張嘴呼吸。除了把關節的問題弄得更嚴重之外,連臉型也跟著跑掉(連家人都說怎麼下巴比起以前更凸了),但是因為不知道解決方法所以還是一直放著沒弄。 台灣牙醫興健保 也是因為疫情關系,香港很多公司也沒再在招人。本來我大學是讀 CS 相關的,畢業之後大概除了香港科學園之外(開發類),只能跑去當 IT(當狗 當技術支援與整合)。在疫情的環境下,CS 的工作大減,IT 需求大增;但是作為 CS 出生寫 code 技術滿不錯的畢業生來說,去當 IT 反而有一種「降等」的感覺。當時台灣還沒受疫情波及,所以我決定去試試找台灣的工作。但是在網上看了一轉,台灣大部分(有一定程度技術需求)的工作都需要碩士畢業學歷,所以我就選了一家南部的四大再進修一下,邊留意市場狀況。 到了台灣半年左右知道了健保有每半年免費洗牙之後,我便間中在下課之後去附近的牙醫診所洗牙(畢竟有免費的不用有點對不起自己)。那間診所是由一位成大出身的老牙醫經營,洗牙完全不痛,一看就知道是大師級的人物。有一次去洗的時候好奇問了一下牙醫我這情況還有救嗎?他伸手摸一下我的下顎然後按住下巴開合一下,說「你這情況需要動手術」,我才知道原來還有這治療方法!?然後我就問他要了轉診單找了推薦的醫師去成大醫院咨詢。 去到成大醫院之後找到了老牙醫推薦的口腔顎面外科王東堯醫師,不問不知道,一問嚇一跳,原來他是台大畢業的香港人,所以才有這麼高的素質。來回好幾次照了 X 光 CT 之後決定了要開 BSSO(就是比較複雜的 multi-axis 旋轉 + 移動的那種正顎,並非那種單純縮下巴 / 拉出上顎的單軸式移動正顎),加上我的頭骨可能對於男性一般值來講也是偏細 + 薄,CT 掃出來重建的 3D 模型也一大堆穿洞(太薄的骨頭掃不出來),最後委托外面的 3D 醫學模型公司來做 3D 模型重建 + 特別設計手術用的咬合板(因為 BSSO 上下顎都要切開,所以需要設計幾個定位用類似牙模的東西來讓醫師開刀的時候知道骨頭要移動多少)。 在我做最後決定前,因為尊重原因我有先知會我的父母。我媽就是很擔心,所以把我拉去香港的診所要醫療意見。跑了好幾個禮拜之後大概的結論是 香港也不是不能做,只不過可能要排 6 - 8 年;要快的話可以去私家,我這個 case 整套下來大約要花 40 - 60 萬港紙(相比起台灣找大醫院做的話大約是 40 萬台幣左右)香港的做法會偏保守,一般會先矯正 1 - 2 年,之後開完正顎之後再矯正 1 - 2 年;台灣這邊王醫師開出來的是 surgery first approach,可以省下一開始的矯正,整體治療時長會減少到 1.5年 左右兩位香港的醫師看完台灣醫師給的治療計劃後都覺得沒甚麼問題,甚至有一位聽到台灣開的價格之後主動建議「你們還是在台灣開比較好」 所以後來就按原定計劃在台灣開刀了。 檢查與開刀經歷 開刀其實不麻煩(畢竟麻煩的是醫師),最麻煩的事情大概就是每隔幾個禮貌要回去一次檢查這個那個甚麼的。這個我覺得比較少人有寫到網絡上分享,大概就是 X 光檢查、驗血、拍照記錄CT 掃描(取得頭骨 3D 模型)制作咬合板,用來於手術中移動上下顎骨頭的時候可以準確的對到設計位置(順便一提,這是用醫療級 SLA 3D 列印做出來的,很酷)備血(兩次,隔大約一個月,以防不時之需) 進開刀房 開刀其實沒甚麼大不了(畢竟對我來說睡一覺醒過來不是看到醫生就是轉生異世界),大概跟其他網上分享的經歷差不多:前一天先住院做檢查,然後就是等第二天早上開刀。開刀前 12 小時不能吃喝,然後開刀大概 8 - 10 個小時,但是對我來說就是睡覺醒來就好了。 這一覺我猜連續睡了快 2 天(手術 + ICU) Propofol 真是個好東西,醒來的速度也有夠快。醒過來之後第一件事大概感覺得呼吸很困難,然後就慢慢感覺到氣管裡有種卡住東西但是也能呼吸的感覺。可能差不多這個時候醫師發現了我可以自主呼吸,就來幫我把插在氣管裡的呼吸機喉管拔掉,這個時候就舒服一點了。接著躺著休息一陣子之後被轉移到另一張床上,然後馬上被推進一般病房去。(果然 ICU 的床位還是很搶手的) 進去之後大概就是抱著我的 IKEA 鯊鯊在睡,間中會被叫起來照 X 光之類的。每朝早上王醫師來查房的時候都會有這樣的對話: 王醫師:你覺得怎樣?我:很腫王:你已經不算腫的了 剛被推出 ICU 的時候會有一條從下巴拉到頭頂的繃帶用來固住定住下顎。因為本來你的臉已經是在腫了,還要被繃帶壓住,所以一開始真的超級不舒服。然後大概在 2 / 3 天之後,用來固定的繃帶會被拆開,這個時候才真的比較可以講話和看到明顯的術後變化。 整體來說,術後大概躺了 1.5 天深切治療部(加護病房 / ICU),睡了 4 天醫院一般病房(其中兩天不能吃喝,但是因為有輸葡萄糖液所以完全不覺得餓),之後連續喝純液體 2 個星期(應該沒甚麼人能體會到連續喝舒跑 + 寶礦力兩個星期的慘況) + 4 個星期流質之後,在第二個月終於可以吃到比較軟的食物(如粥、布丁之類的),第三個月才能吃飯。雖然說開刀是沒開到關節的位置,但是因為整個下顎的部分都腫起來,不要說咬合,實際上連把食物吞進去的動作也沒辦法到(因為這時候還沒矯正,牙齒還是在 open bite 的狀態 ,加上肌肉不知道為甚麼沒辦法發力),所以吃流質的時候是需要用針管透過一條橡膠管插進喉嚨裡面,然後把液體往裡面擠來「吃飯」。 術後矯正 在大約術後一個月左右開始來做術後矯正,然後裡就是一般牙齒的矯正過程了。由於(醫師說)牙齒在骨頭開刀之後會動很快,所以矯正也應該只需要 1 - 1.5 年就好。跟著那幾個月就是一般牙齒矯正的各種慘況:牙齒很酸,沒辦法吃硬的東西之類的,但是比起正顎後那一個月來說還是舒適很多。 雖然說整個過程都超級辛苦,但是自從開完半年左右,好處就慢慢出現了。首先當然是咬合變好顎關節痛的頻率也減少了很多,同時間附帶的是臉部線條變順暢;其次就是終於可以用鼻子呼吸而不是嘴巴,所以睡覺品質也變得更好;而加上因為有讓醫師在全身麻醉的時候順便把智齒都拔掉,所以之後也不用擔心那會有蛀牙問題( 一年後的想法 我對正顎的想法大概就是很值得做,無論是在功能上還是生活品質上面都有很好的改善。唯一的可惜就是我沒有早點做(聽說大概 21 - 22 歲做會更好),有人說早點做的話神經線恢複的速度會更快,但是我覺得還好(? 另外由於健保給付了一部分的病房 跟 X 光 費用,所以出院的時候除了手術、開刀房借用跟升級藥物到比較好的止痛藥等的費用之外,基本上都包含在健保內。當時心裡面是有點過不去(畢竟我又沒付多少健保費),但是近幾個月開始在台灣半導體業工作,每月收到薪資單看到被扣的健保費用之後心裡還是有好過一點。。。
ffmpeg HTTP 串流 HDMI 採集卡輸入
如果有一天不知道為甚麼你需要透過 Linux / Raspberry Pi 串流 HDMI capture card 到網絡上另一個位置的話,你可以用這個 ffmpeg command: ffmpeg -f v4l2 \ -input_format mjpeg \ -video_size 1920x1080 \ -framerate 25 \ -i /dev/video0 \ -vcodec mpeg4 \ -pix_fmt yuv420p \ -q:v 1 \ -f mpegts \ -flush_packets 1 \ -max_delay 0\ -analyzeduration 10 \ -probesize 32 \ -fflags nobuffer \ -tune zerolatency \ -listen 1 \ http://0.0.0.0:8088/feed.mp4 如果你的電腦上並沒有 v4l2,可以透過以下指令安裝並抓到 capture card 支援的輸出格式 sudo apt-get install v4l-utils v4l2-ctl --device=/dev/video0 --all 輸出例子: Driver Info: Driver name : uvcvideo Card type : usb video: usb video Bus info : usb-0000:00:14.0-2 Driver version : 6.1.119 Capabilities : 0x84a00001 Video Capture Metadata Capture Streaming Extended Pix Format Device Capabilities Device Caps : 0x04200001 Video Capture Streaming Extended Pix Format Media Driver Info: Driver name : uvcvideo Model : usb video: usb video Serial : Bus info : usb-0000:00:14.0-2 Media version : 6.1.119 Hardware revision: 0x00002100 (8448) Driver version : 6.1.119 Interface Info: ID : 0x03000002 Type : V4L Video Entity Info: ID : 0x00000001 (1) Name : usb video: usb video Function : V4L2 I/O Flags : default Pad 0x01000007 :…
Type C source & sink 的 CC pin 電路設計
因為最近我的專案都全面轉用 type C 來取代傳統的 micro USB 跟 type A USB port 了,避免開始工作一段時間之後忘掉了怎樣走線,所以就來寫一篇部落格來記錄一下。 Type C Sink (接收端,例如說要充電的裝置) 這個官方文件裡面是說需要把 CC 接 5.1K 到 GND。可是由於一開始規格內容沒很明顯,導致不少人誤解了這句話的意思,例如說 Raspberry Pi 4 一開始版本的 type C sink 設計就是有問題的: Raspberry Pi 4 錯誤的 type C sink 下拉電阻設計 一般來說如果你的電源供應器只輸出 5V 3A 的話倒是沒甚麼問題,但是一用到支持 E-mark 的 type C 線加上 PD 供電器就會出問題了。所以正確的接法應該像下面的, CC1 跟 CC2 獨立拉出來經 5.1K 電阻再接到 GND 上面去 我設計的 type C sink,注意 CC1 跟 CC2 是分開接到 5.1k 的 這樣 PD 電供就會知道這是一個 5V 的 sink 而輸出 5V 3A 的電壓 / 流了。 Type C Source (放電端,例如說充電器或是筆電) 接收端應該是比較多人知道,但是 Source 端就較少人討論了。技術宅老人可能知道,在很早的電子產品中, USB 接口預設最高只能提供 (5V)500mA 的電流。所以如果隨便把現代的電子產品插到 10 - 15年前的電腦上充電,電腦有時候會直接把那個 USB 口砍掉( 但是因為太多人拿 USB 來當一個充電接口,所以後來 USB-IF 加入了額外的規格來給需要 USB 充電的裝置使用,現在大家看到的 5V 1A、2A 到 type C 的 3A,都是後來 USB Battery Charging Specification (BCS) 跟 USB Power-Delivery (PD) 提供的。 因此在此背景下, type C device (特別是能用到 10W 以上的電子產品)在插上去的時候,是需要知道 Source 能提供多少的電流,是甚麼規格下的 USB 端口才敢開始充電。 source 的 pull-up 電阻規格 但是就一般 DIY 電路板來說,如果是做信號類的東西,我們就只要在 Source 端 CC1 跟 CC2 分別接個 56K 上拉(即 CC1 / 2 經 56k 到 VBUS (5V))就可以了。如果你在做的 source 是一個 5V 的充電器,能提供到 1.5A / 3A 的話,也可以按上表中改用 22k 或是 10k 的上拉電阻。例如說 Github 上這個開源的 type C USB hub 就是如此設計。
香港人在新竹租房奇遇記(?)
最近因為在新竹科學園區找到了算是人生第一份正規的科技業工作,所以就準備打算直接搬家到新竹東區來方便上下班。然而想不到新竹的租房市場真的比想像中和資料收集中的還要坑人(特別是新手),所以就來寫一篇部落格來記錄一下。 事前資料收集 第一次做任何事情之前,最重要的就是做資料收集了。首先,在新竹科學園區附近找房子,有幾個重點: 591 上面的都是釣魚的Facebook 租屋群,騙子超多,下面再詳細解釋本地的不動產(門市),通常都只會登出超難租出或是貴價的,學長說那種風險很高不建議考慮掃街(建議的方法,但是很浪費時間和體力) 591 新竹租房問題 可能對於第一次來台灣的香港人,最早接觸到的租(買)房平台就是 591 了。可是在新竹的租房情況下,591 一般會出現釣魚問題。 簡單來說是 591 上面都只有仲介,放出來的圖片是真的,物件也應該是真的,但是你看到的時候通常都已經租出了。當你真的約房仲出來看的時候(就是你人已經去到新竹的時候),他才跟你說房子已經沒了,要不要看其他的? 如果你回答【好喔】,那你就踩進去這個陷阱了。他會把他手上有的租不出去的爛房子都給你看(網上也有人說他會假裝又有房子被租走了,來讓你趕快決定),然後很多人就因為「已經來了,不能空手而回」的想法而租到爛的物件。所以一般來說除非你真的沒辦法多來幾次,而且對房子的品質沒甚麼考慮的話,否則一般都不推薦這個方法。 Facebook 租屋群組 這又是另一個大坑,裡面就是房仲跟騙子的聚集地。其中一個最大的群組就是這個: 這個群組上主要會看到三類帖子,第一類是房仲,第二類是騙子,第三類是房仲或是騙子裝的房東自租。 房仲 房仲就比較簡單,你看到有浮水印的,多數都是房仲所推出來的房子。一般來說都會附上房仲的登記號碼(我猜政府應該是有管理制度,可以查詢到該房仲是不是合法經營的)。一般來說透過房仲找的話對方會多收你租金的 50% 作為中介費(一次性),所以如果你是很忙沒時間找而且不怕價格偏高的話,透過房仲來找房也不免是一個快捷省時間的方法。至於哪家房仲或是不動產公司比較好,這個你可以參考台灣 ptt 或是 dcard 上面的帖文。 浮水印例子(一) 浮水印例子(二) 網絡騙子 騙子的概念大概就是要來騙你訂金的,因為通常這類帖子都是由機械人或是詐騙集團發出來,背後大概有個模板所以就比較好分辨了。如果你看到類似這種的,有很大機率都是假的租房帖文。 關閉留言功能的 要求「優質房客」 房東自租 這個就要特別注意了。雖然說確實會有機會是有房間是由房東自己釋出,但是更多時候是房仲騙你去看房或是騙子的技巧。例如說這種的,資料看上去是真的可是把回應功能關掉的;或是寫著「可預訂」的,一般都是假的。 一般來說房東自租的物件真的超級少,你要到當地掃街才會有機會找到,所以在 FB 或是 ptt 上能找到的,即使真的是房東自租,你也不應該去看(價格不合理、環境差等而導致沒人租)。 本地的不動產 當你人已經去到新竹後,你可以選擇到附近的不動產門市看看。一般來說跟你在 FB 上看到的差不多,但是沒有了騙子與房東自租的部分,所以基本上貼在店外的就是 What you see is what you get 類房間。如果有看到有興趣、價錢 ok 的話直接進去說要看房就好了。然而,在科學園區工作了好幾年的學長卻建議不要找這種的,風險很高(我也不太懂),但是基於前人經驗之談,我在這次找房間的時候就直接跳過這個選項。 掃街 最後我決定直接掃街,從金山前到後每條巷子都走一趟。這邊的文化我覺得比較有趣的是,很多房子外面都貼著「套房出租」的 banner。 這種打電話過去有機會是房仲,也有可能是房東,這完全要看你運氣;有時候打電話過去會被告知已經全租出了,也有可能是同一個房仲在其他地方會有其他房間等等的。再加上這附近的人口超多流動率也有夠高,根據我的抽樣統計在我找房子的這個月這附近大約有 23 - 25 間空房出現(以及被租走),所以如果第一天找不到的話可以多待幾天再找,到時候可能就能找到了。 關於外國人租房的這回事 基本想如果你想在科學園附近租房的話,有兩樣東西是必要的 園區內公司之工作證件或報到通知單居留證(部分房東對於外國人特別抗拒,所以這個也很看運氣)或 台灣人當保證人 第一項這倒是不大問題(你要是沒在園區找到工作,搬過去幹麼?),問題是第 2 跟 3 項。在我找房的過程中,遇到最多的是當房東或房仲知道我是香港人的時候,他就馬上拒絕租給我(大部分都是直接說不考慮租給外國人)。 另外我也有找到另一間房東自租的房子,房東是高雄人,人滿好的,可是聊了很久之後打算租下去的時候才跟我說需要保證人才能簽約。單單是這幾點我就完全感受到台灣老一輩人排外的文化( 所以簡單來說,如果你是本地人的話房子已經夠難找了,如果是外地(國)人的話這個就更難了。就我找房的這兩次新竹之旅來說,大概 70 - 80% 都是需要保證人 或是 不租外國人的,所以如果你真的像我這樣要在新竹找房子,記得一定要預留至少 4 - 5 天時間。 我的找房經歷 由於我還沒有考到機車駕照,在只有單車跟步行的情況下,我選擇了金山街一帶的房子。那邊在當地人口中被叫做「新手村」,作為「新手」,住「新手村」就最合適不過了。我第一次去那邊的時候是搭高鐵,到新莊車站之後走路到附近的公車(巴士)站,再轉科學園紅線進去科學園;面試完之後再經科學園路走到金山街。 之後第二次去新竹的時候因為不用去面試,可以直接去金山街,所以這次我選擇搭火車(台鐵) 到新莊車站 再走路過去。雖然說有一部分路上次有走過,想著應該後面那段都是差不多,結果走過光復路一段之後全是上坡,走到快要體力不支( 後來第二天繼續去掃街的時候我選擇直接搭去竹中車站,這樣走路的距離在地圖上看短了不少 中間一開始看到有一片綠色我還以為是一個公園,怎知道去到之後才發現是一個山坡(結果又是上坡,果然金「山」街真的在山上的啊) 這山坡大概有 4 - 5 層樓高,而且還要是這種小徑的樓梯有夠難走 還好走完這一次之後我終於找到合適的房子並下了訂金(下次去的時候應該就會直接「的士 ~」) 結論 如果你找到了在新竹科學園區的工作,雖然很恭喜你,但是最難的部分才剛開始。之後還要考慮到住宿、交通(聽說在新竹的話必須要懂得騎車,雖然說 Ubike 看上去也滿多的)跟食的問題(聽說除了火鍋跟 M 記以外都沒好吃的),所以適應力的技能點也要點好點滿才進去喔(
Recursion vs Dynamic Programming
剛剛我在寫 Leetcode 的 70. Climbing Stairs 沒留意到它是考 DP 寫成了 Recursion,結果跑起來 timeout。剛好我覺得這是一個很好的 Recursion vs DP 的範例,我就拿出來解釋一下好了。首先,這是我寫的 Recursion 解法: class Solution { public: int sol = 0; int climbStairs(int n) { f(n, 0); //把 sum 設為 0 return sol; } void f(int n, int sum){ if (sum > n) return; //超出了目標位置 if (sum == n){ sol++; //這是其中一個爬法 return; } //跟終點還有點距離,試試 f(n, sum+1); //爬一步 f(n, sum+2); //爬兩步 } }; 每爬一段之後,檢查是不是已經到終點了,如否,再分支出兩種走法 - 走一步 與 走兩步。 這個我覺得是最簡單易懂的寫法(甚至已經是教科書等級的 recursive function 了),但是居然跑到 timeout 這個我還真的沒想到。所以後來只好換成 space-time tradeoff 的 dynamic programming 寫法: class Solution { public: int climbStairs(int n) { int dp[46]; //因為題目說最多 n = 45 dp[1] = 1; //爬到第一格,只有一個爬法就是爬一格 dp[2] = 2; //爬到第二格,只有 1 + 1 跟 2 兩種爬法 for(int i = 3; i <= n; i++){ //從 3 開始,就是前兩格爬法的總和 dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; // 最後回傳能爬到最後一格的總可能路線 } }; 果然是做嵌入式開發做得多,寫出來都是以最佳化 memory consumption 的職業病(
Leetcode 加速器 Cheat Code (C++)
最近因為工作面試的關系,我都在練習寫 Leetcode (畢竟從大學畢業之後我的主力語言都是 C (MCU / low level)、Go (Linux / Networking Applications)跟 網頁(HTML 、CSS 、JS 那堆)。雖然我都跟我面試的主管說「我都沒在寫 Leetcode」(因為我不想他們以「平常有寫多少 Leetcode 」為顧用考慮之一),但是或多或少因為有一段時間沒寫 C++ 了所以還是要找些免費題目來抓回以前的感覺。 最近我在寫 LeetCode 的時候發現一個滿有趣的現像,就是一般很多題目都會有一些特別特殊的極端例子(超快、或是超省記憶體)的情況。去翻一翻這些 code sample 之後我發現了一段很有趣的 code。 int speedUp = [] {std::ios::sync_with_stdio(0); std::cin.tie(0); return 0; }(); bool has[100002]; int digit(char c) { return c & 15; } bool isDigit(char c) { return '0' <= c && c <= '9'; } int init = [] { std::ofstream out("user.out"); std::cout.rdbuf(out.rdbuf()); for (string s; std::getline(std::cin, s); std::cout << '\n') { int n = count(s.begin(), s.end(), ',') + 3; memset(has, 0, n); for (int _i = 1, _n = s.length(); _i < _n; ++_i) { if (s[_i] == '-')for (_i += 2; isDigit(s[_i]); ++_i); else { int v = s[_i++] & 15; while (isDigit(s[_i])) v = v * 10 + digit(s[_i++]); if (0 < v && v < n) has[v] = true; } } for (int i = 1;; ++i) if (!has[i]) { std::cout << i; break; } } exit(0); return 0; }(); 沒錯,是一段 C++ 的 runtime cheat code。先上面的部分開始看,這邊它使用了 lambda function 來透過「假裝」定義 speedup variable 來執行了一些 STDIO redirect 的功能。我猜它應該是把之後執行的東西的 STDOUT 跟 parent 的 STDIN…
DIY 開源智能手錶(選螢幕篇)
最近因為各種原因想買一只智能(慧)手錶,可是由於我只喜歡方形的,所以剩下的選擇大概只有智慧手環、大號智慧手環跟 Apple Watch 可以選了。嘛,其實除了這些還有其他的啦,然而市場上就一堆很奇怪的選擇都沒一個完全對得上我想要的東西。 小米手環 8 Pro / 華為手環 8 :性價比跟功能都不錯,可是螢幕是像一般手環般長方形的不是方形Venu Sq 2:功能超棒,但是這個配色的品味只能說很獨特(?Fitbit (Google 旗下)Versa 4:這外形和黑邊遠看還以為是玩具Apple Watch SE:功能和外形都很棒,就是只支持 iOS 跟需要每天一充 說實話我覺得最好看的設計一定是屬於 Sony 的 Smartwatch 3 了。如果把這個設計換上最新的 AMOLED 螢幕和把那個用久了會黏手的 rubber wrist band 換成其他材質(如 silicon / TPU)就更完美了。 所以作為一個 DIY 愛好者,很自然的我會去想:假設我要自己做一只智能手錶的話,我會怎樣設計呢?首先,我想到的大概是以下的一些技術規格: 基於 ESP32 C3 的方案(在寫本文的時候最新支援 BLE + WiFi 的 MCU)AMOLED 螢幕 + 環境光傳感器(自動亮度調整)內置基本的傳感器(光學心跳 + 血氧、accelerometer(步數)、gyroscope(移動距離),如果有空位的話可以再塞個地磁 sensor 做指南針之類的) 然而有一些我也很想做的部分,是現在很多便宜的智能手錶沒有的 無線充電(對,現在很多大號手環還是在用磁石彈針來充電)Cloud First 資料傳輸(就是以 WiFi 為先,資料先到伺服器再同步到其他手機和裝置,有點像 ArozOS 的設計邏輯)開源、可由使用者自行編程(基於 ESP32 Arduino 內核 + LVGL?) 螢幕選擇 要做到上面的東西其實不難,最難的部分應該說是螢幕。螢幕這東西不像 電路板 可以隨便畫一畫送出去就能做,而是需要很大量的定制才會有這樣的產品。所以在設計這只手錶的電路板之前,就要先決定好手錶的外殼大小,而外殼大小就是取決於螢幕大小。 我覺得我也不是第一個遇到這樣困難的開發者。例如說著名的 Pebble 智能手錶之所以是長這樣是完全因為生產它的公司被 Sharp 生產的 Memory LCD display (1.33 inch)所限制,所以只能夠圍繞著這個當時唯一一片 high refresh rate 又省電的黑白螢幕來開發整個產品。 初代 Pebble 智能手錶 SHARP 1.33 inch Memory LCD display 雖然說現在很多智能手錶的出現而讓市場上多了很多 1.3 - 1.9 寸的螢幕,但是這又產生出另一堆問題: 便宜的智能手錶很多時候都會使用 TFT (IPS)螢幕大部分現有的 AMOLED 螢幕都是圓的很多做螢幕的廠商都只做螢幕、做電容觸控層的都只做觸控層、要找到非現有產品在用的螢幕總成很困難 單單是後兩個問題就把我能選擇的螢幕大小限制住了。把圓的 AMOLED 跟沒有觸控的去除掉之後,剩下的就只有 1.69 inch ,在開源 smartwatch project 很常見到的 waveshare 模組以外就差不多沒其他選擇了。 不過不得不說,這 1.69 雖然在黑色背景的畫面下看上去還不錯,但是一但亮起來你就會發現它黑邊粗得要死,還要上下闊度不同!(噴血 AMOLED 螢幕最佳選 然後就到這個,這是我在某中國的螢幕供應商那邊看到這片要價快 6 美元的 1.78 寸 AMOLED 螢幕,368 * 448 pixel https://www.dwo.net.cn/pd.jsp?id=11921 這個數字是不是有點眼熟,沒錯,這就是 Apple Watch SE 44mm 在用的那一片螢幕( 上下黑邊總於是一樣的了 只是說這片螢幕要用 ESP32 驅動會有點小問題:這東西只支援 SPI、QSPI 跟 MIPI DSI。MIPI DSI 先不用說了(這東西要用 arm 處理器才推得動),剩下的只有 SPI 跟 QSPI 能用。SPI 的問題在於即使是 ESP32 用 SPI half_speed (最高 80Mhz,但是考慮到 FreeRTOS 後面還有其他東西要跑,先把一半的 CPU cycle 預留給其他 vTasks 好了)速度來驅動它,你還是會看到動畫像 ppt 一樣(我猜的),但是我把計算資料塞給 chatGPT 後它也告訴我大概只能跑到 15.16 FPS,所以應該是肉眼可見的卡(? 這樣我就剩下 QSPI 可以選擇了。QSPI 大概就是拿四個針腳來推資料,理論上應該會快上不少。ChatGPT 說大概能推到 60fps,但是我先保守估計抓個 30…
Golang trie tree IP 轉國碼(Country Code)
最近因為有人 report 說 Zoraxy 的 GeoIP 功能不準,於是我在重新 review 這個 geoip database module 的時候發現:欸幹真的寫錯了欸!? 然後在花了一整個晚上 debug 之後,我終於找到正確的演算法和做法了。這裡為了省下後人在掘怎樣用 Golang 寫 IP 轉 Country Code 的 resolver,我這裡給大家一個快速上手的教學。 甚麼是 IP 轉 CC? IP 就是 IP 地址,CC 是 country code,有時稱為 ISO 國碼,例如說 HK TW US GB 之類的。IPv4 跟 v6 的地址區間通常會被 assign 到某一個國家的 ISP 上,而造成說只要知道 IP 地址,某程度上你可以知道那個 request 是從哪個國家出來的(先不要說 VPN 或是 ip spoofing 之類的部分的話)。可是由於每隔一段時間就會有 ISP 倒閉、收購或是重組之類的,所以 IP 轉 CC 的可靠度並非 100% 準確,這個時候我們就需要從一個可靠的來源來定期更新這一個 IP range 到 CC 的 mapping database 了。 這個 database 最出名的應該就是 maxmind 的 geoip database,人家都整理好所有數據到一個 database 檔案裡面,也提供 api 讓你可以快速查詢 cc,很多現代只會拉 library 的開發者通常就會直接選用它。然而,基於它的 license 問題,如果真接用它的方案的話會讓授權和 licensing 變得一團亂,所以基於多種考慮之下這個方案我們最終沒有使用,而是使用 Public domain 或 CC-0 的資料來源。 GeoIP Data Source 最後我們使用了來自 GeoFeed + Whois + ASN (CC-0)的資料來源: https://github.com/sapics/ip-location-db/tree/main/geolite2-country 問題是它的資料表是一個 csv 檔案,大概長這樣: 1.0.0.0,1.0.0.255,AU 1.0.1.0,1.0.3.255,CN 1.0.4.0,1.0.7.255,AU 1.0.8.0,1.0.15.255,CN 1.0.16.0,1.0.31.255,JP 1.0.32.0,1.0.63.255,CN 1.0.64.0,1.0.127.255,JP 1.0.128.0,1.0.255.255,TH 1.1.0.0,1.1.0.255,CN 1.1.1.0,1.1.1.255,AU 1.1.2.0,1.1.63.255,CN 1.1.64.0,1.1.127.255,JP .... 223.255.248.0,223.255.251.255,HK 223.255.252.0,223.255.253.255,CN 223.255.254.0,223.255.254.255,SG 223.255.255.0,223.255.255.255,AU 那假設我給你一個 ip 地址:A.B.C.D,你要怎樣把 country code 從 csv 裡面抓出來呢? 最直觀的答案:O(n) 當然如果會一點寫程式的人就會想到:那我把整個 csv loop 一次,看看哪一行的 start ip 跟 end ip 是包含我這個 ip 地址的就好了啦?這個的確是我們做給嵌入式裝置的做法(不過是為了省 memory),原理大概這樣: func isIPv4InRange(startIP, endIP, testIP string) (bool, error) { start := net.ParseIP(startIP) end := net.ParseIP(endIP) test := net.ParseIP(testIP) if start == nil || end == nil || test == nil {…
淺談 CSRF 與 CSRF Middleware
最近在我的 Zoraxy 開源專案那邊出現了這樣的一個 bug issue 他還很好的附了一個網頁測試介面的截圖 甚麼是 CSRF 攻擊? CSRF(Cross-Site Request Forgery,跨站請求偽造)是一種網絡攻擊技術,攻擊者通過在受害者不知情的情況下,冒充受害者的身份在受害者已經認證的網站上執行未經授權的操作。 CSRF 攻擊的工作原理 受害者登錄到可信網站:受害者首先登錄到一個需要認證的網站,在這例子是 Zoraxy 的後台管理頁面攻擊者製造惡意請求:攻擊者創建一個包含惡意請求的網頁或電子郵件,例如說這裡的一個 free burger 網頁 html + web form (action 或 form submit URL 指向 Zoraxy 的後台管理頁面)受害者訪問惡意鏈接:受害者在登錄狀態下 request 了攻擊者控制的網頁並提交了 web-form。執行未經授權的操作:由於受害者已經登錄(Auth Cookie 會跟著 form 一起送出去),Zoraxy webmin 介面會以為這些 request 是 valid 的,並執行相應的操作,例如上面例子的 toggle proxy 開關狀態 。 防禦 CSRF 攻擊的方法 CSRF Token:在每個受保護的操作請求中加入隨機生成的、唯一的 CSRF Token,並驗證這些 Token 以確保請求是合法的。檢查 Referer 和 Origin 頭:檢查 HTTP 頭中的 Referer 和 Origin 字段,確保請求是從合法的來源發出的。但是我在寫的是 Reverse proxy,本來 Referer 跟 origin 就是不可信的,所以不能使用此方法。雙重提交 Cookie:將 CSRF Token 存儲在 Cookie 中,並在請求中一起提交和驗證。使用 SameSite Cookie 屬性:設置 Cookie 的 SameSite 屬性為 Strict 或 Lax,限制跨站請求攜帶 Cookie。 說真的這也不是我第一次處理 csrf 問題。先前同一位使用者也對 ArozOS 提出過類似的 issue report。然而 ArozOS 因為是使用 agile 開發的,API 散落得到處都是,根本沒辦法用標準的方式來做 csrf middleware 來保護 API 接口,所以當時只用了一個非常不標準的方式來做(就是每一個 post request 之前都加一個要求 csrf token 的 ajax request,變成每個 request 都會變成要 request 2 次) csrf middleware csrf middleware 是一個 http Handler 並讓它來對 request 進行預處理並對不合規格的 POST (PUT / DELETE) 等 request 進行攔阻。所以這裡有兩個需要做的事情 在 HTML 頁面生成的時候,在頁面上加入一個 csrf token在有資料需要以 POST 等 request 回傳到伺服器時,在 payload 中以 header field 的方式附上頁面的 csrf token 在 ArozOS 的做法上,它是先對伺服器進行一個 csrf token generation request,然後再把 request 附到下一個 ajax POST request 裡面。這做法雖然不是不行,但是對於前端來說要改實在太複雜,而且一個 csrf token 又沒有那麼快 expire,不斷生的話只會浪費後端資源。 Production grade 的 csrf middleware 使用方法 由於 Zoraxy 的設計從一開始就按著標準的…