Golang 程序連續執行一個月之後一定會出錯之神奇原因及除錯
話說半年前我開發了一個叫 imusutm 的 Simple Up Time Monitor。它是一個很簡單的程式:每隔 5 分鐘去 ping 一次 json 設定檔案裡面的網址,然後把它 buffer 到 RAM。同時提供一個 RESTFUL API 給 php script 來 call 並回傳最近一天的狀態。 可是不知道為甚麼,這程式很固定每隔一個月就會出現全部斷線的狀態,連帶用這系統的 Telegram bot 也一律全部報錯 在無論如何都沒辦法在本地端 reproduce 之後,在上一個月我把所有 error 裡都加入了 error message print-out 來協助除錯。結果被我發現問題了: 2023/02/24 20:20:34 Get "{某個要 ping 的 IP 地址}": dial tcp {某個要 ping 的 IP 地址}: bind: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full. 一看到這個我就明白了,原本是 socket 用光了。可是一台主機的 socket 有這麼多怎可能能全部佔用? 原來是忘記了 resp.Body.Close() 於是我去看整段 code 唯一有用到 socket 的部分:http.Get(url),發現忘記了寫 resp.Body.Close() 了(222 行是新加入的),所以難怪會出現這個問題。 可是,又為甚麼是大約一個月會出現一次? Windows 的 Dynamic Port 與 ArozOS Windows 的 dynamic port range 是由 49152 到 65535。雖然有一些間中會被佔用,但是大部分都是用完之後就會被釋放出來。這兩個數字相減之後便能得出 16384 個能用的 dynamic port。 至於為甚麼是一個月?因為 每小時有 12 個 5 分鐘 x 一天 24小時 x 30天 = 8640 個 connection,然後由於 ArozOS 使用的 Go net/http 會自動斷開,而這數字又差不多是可用 port 數的一半,再加上列表上又剛好只有兩個 connection 是能維持的,我好像猜到點甚麼的樣子(? 總括而言 相信加入了 resp.Body.Close() 之後就能解決 utm 不穩定的問題了,如果下一個月再出現類似的問題我還是整個砍掉重寫好了(畢竟這只是一個我在等上飛機時的小作品,出現這些奇怪的 bug 也不怎麼意外就是了)
在 Raspberry Pi Zero 2W 上設定 WiFi AP 作為 WiFi 中繼器
因為不知道為甚麼網上沒有 Raspberry Pi Zero 2W 以外接 WiFi USB 作為 WiFi 中繼器的教學,所以我就來自己研究出一個方法囉 材料 Raspberry Pi Zero 2WRaspberry Pi OS 用作 wlan1 的 USB WiFi 模組 安裝所需 Package sudo apt-get update sudo apt-get upgrade sudo apt-get install hostapd sudo apt-get install dnsmasq sudo systemctl stop hostapd sudo systemctl stop dnsmasq 設定網絡界面卡 sudo mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig sudo nano /etc/dnsmasq.conf 寫入以下內容 interface=wlan1 dhcp-range=192.168.0.11,192.168.0.30,255.255.255.0,24h 固定 wlan1 的 IP 地址 sudo nano /etc/network/interfaces 把 source /etc/network/interfaces.d/* 加上 comment: #source /etc/network/interfaces.d/* 寫入以下內容 auto lo iface lo inet loopback auto wlan0 iface wlan0 inet dhcp allow-hotplug wlan1 iface wlan1 inet static address 192.168.0.10 netmask 255.255.255.0 broadcast 192.168.0.255 gateway 192.168.0.254 設定 Hostapd sudo nano /etc/hostapd/hostapd.conf 寫入以下內容(記得更新 WiFi 名稱與密碼) interface=wlan1 hw_mode=g channel=7 wmm_enabled=0 macaddr_acl=0 auth_algs=1 ignore_broadcast_ssid=0 wpa=2 wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP rsn_pairwise=CCMP ssid=[WiFi SSID 名稱] wpa_passphrase=[WIFI 密碼] 之後編輯預設設定 sudo nano /etc/default/hostapd 把原本的 #DAEMON_CONF="" 改成 DAEMON_CONF="/etc/hostapd/hostapd.conf" 設定 wlan1 -> wlan0 的 forwarding sudo nano /etc/sysctl.conf 把 #net.ipv4.ip_forward=1 改成 net.ipv4.ip_forward=1 新增 iptables 規則 如果你是用 Lite 版沒有 iptables 可以先透過以下指令安裝 sudo apt-get install iptables 然後輸入 sudo iptables -t nat -A POSTROUTING -o wlan1 -j MASQUERADE sudo sh -c "iptables-save > /etc/iptables.ipv4.nat" 編輯 rc.local 在啟動時自動應用規則 sudo nano…
JavaScript 顯示時間的例子
因為間中要用到每次都要去 Stack Overflow 抓有點麻煩所以就直接記下來了: new Date().toLocaleDateString() > 2022/1/30 new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}) > 2022年01月30日 星期日 01:05:48 new Date().toLocaleDateString('en-US', {year: 'numeric', month: '2-digit', day: '2-digit'}) > 01/30/2022 new Date().toLocaleDateString('en-ZA') > 2022/01/30 new Date().toLocaleDateString('en-CA') > 2022-01-30 new Date().toLocaleString("en-US", {timeZone: "America/New_York"}) > 1/29/2022, 12:05:48 PM new Date().toLocaleString("en-US", {hour: '2-digit', hour12: false, timeZone: "America/New_York"}) > 12
透過 Arduino 取得 UART HMI 螢幕頁面 ID 然後按需求轉頁
最近因為我在弄一個 60W 的 PD 充電器,然後想弄一個迷你的螢幕來顯示電池的資訊,所以我便選用了一個 2.2寸的 UART HMI 了。 2.2 寸的 UART HMI,比想像中要小一點 選 UART HMI 的原因有很多,網上也有很多文章教你怎樣選合適的螢幕,所以我就不細說了。簡單來說, UART HMI 是一種可以透過 Serial 來控制螢幕載入預先設計好的界面的一種人機界面。 發送指令到螢幕 HMISerial 是 Software Serial。以下 function 把 cmd 內的內容發到螢幕,如果 debugMode 被啟用,側會同步輸出到 hardware serial 上。 SoftwareSerial HMISerial(2, 3); // RX, TX //... void sendCommand(String cmd){ if (debugMode){ //Mirror output to serial Serial.print(cmd); Serial.write(0XFF); Serial.write(0XFF); Serial.write(0XFF); } HMISerial.print(cmd); HMISerial.write(0XFF); HMISerial.write(0XFF); HMISerial.write(0XFF); delay(50); } 使用例子: sendCommand("t0.txt=\"[info] MCU Connected\""); 取得現在 HMI 屏幕正在顯示的 Page ID 如果你把 sendme 指令發到屏幕,屏幕會回傳現在的 page ID 給你,它的回傳信號大約長這樣 66 01 FF FF FF 66 是這個指令的回傳碼,01 是現在的 page ID (即是 page 1),FF FF FF 側是傳送完成的意思,所以我們只需要在 Serial.read() 的時候抓到 0x66 就知道下一個一定是 page ID 了。 int getPageNumber(){ sendCommand("sendme"); bool nextReadIsPageNumber = false; while (HMISerial.available() > 0) { // read the incoming byte: incomingByte = HMISerial.read(); if (nextReadIsPageNumber){ //這是 page ID nextReadIsPageNumber = false; return incomingByte; } if (incomingByte == 0x66){ //下一個出現的 byte 就是 page ID 了 nextReadIsPageNumber = true; } } } 使用例子(檢查現在是否在 page 0(hex: 0x00)) currentPageNumber = getPageNumber(); if (currentPageNumber == 0x00){ //Do something } 成果(Arduino 透過 COM port 輸入到模擬器)
Babylon.js ExtrudeShape 匯出成 STL 之後出現不正常 face data 的解決方案
在開發 aPrint 系統的時候,其中一個功能是使用 CSG 把兩個 Mesh 組合成一個容器。可是在組合匯出之後卻出現 face data 重疊的情況(也有人會把它叫做 z-fighting 或是 invalid normal ) 在 Windows 的 3D viewer 中可以看到全部面都有一些問題 這個我研究了一段時間,發現這是跟 extrude 模型的時候的設定有關。一般來說,為了避免模型在背面出現破圖,所以在 render 的時候也會把 rendering face 設成雙面。以下為其中一個例子 BABYLON.MeshBuilder.ExtrudeShape("container-outerwall", { shape: externalBorder, path: [extrusionStart,extrusionEnd], cap: BABYLON.Mesh.CAP_ALL, sideOrientation: BABYLON.Mesh.DOUBLESIDE }, scene); 可是這樣在 CSG 處理的時候卻會出現問題,我懷疑是與 Mesh.CAPALL 的設定有關,可是我並沒有深入研究是不是 CAP_ALL 設定了之後就不能使用 DOUBLESIDE 的設定。 然而在把sideOrientation 改成 FONTSIDE 之後問題就解決了。 BABYLON.MeshBuilder.ExtrudeShape("container-outerwall", { shape: externalBorder, path: [extrusionStart,extrusionEnd], cap: BABYLON.Mesh.CAP_ALL, sideOrientation: BABYLON.Mesh.FRONTSIDE }, scene); 所以結論就是如果你要進行 CSG 操作的話 sideOrientation 不要設成 DOUBLESIDE 就對了。
Refused to execute script from ‘xxx’ because its MIME type (‘text/plain’) is not executable.
Go 在 Windows 10 上使用 File Server 傳送 JavaScript 的時候間中會出現這個錯誤,網上的其中一個解決方法是更改系統 register 讓 Windows 把 JavaScript 辨認為 text/javascript。然而如果你不想透過更改系統設定的方法解決的話你也可以透過編程方法解決。 使用 Router 來手動設定正確的 Mime Type func mrouter(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if filepath.Ext(r.RequestURI) == ".js" { //Requesting a js file w.Header().Add("Content-Type", "text/javascript") h.ServeHTTP(w, r) } else { h.ServeHTTP(w, r) } }) } 使用方法如下 fs := http.FileServer(http.Dir("./web")) http.Handle("/", mrouter(fs)) 這樣便修好了