開發歷程:把手機變成一個即時感測器資料源

這是 LocalPapa Notes 開發紀錄系列的第七篇。上一篇我們做了一個手機感測器儀表板,能在螢幕上即時看到水平儀、指南針、G 力。但很快就有了下一個念頭:如果這些數據能即時送到電腦上,不就成了一個現成的感測器資料源?

於是這個工具長出了「串流」能力。

從「看」到「送」

原本的儀表板是給人看的。串流功能則是給機器用的:把加速度計、陀螺儀、指南針、重力、G 力這些數據,定頻打包成 JSON,即時送往你指定的接收端。

你可以選每秒送 10、30 或 60 次(10/30/60Hz),用 setInterval 穩定發送。一支手機,瞬間變成一個低成本的 IMU(慣性測量單元)資料源——拿來做機器學習資料蒐集、機器人遙測、互動裝置都行。

三種傳輸,給不同場景

我們提供三種把資料送出去的方式:

  • WebSocket(ws://:低延遲、雙向,適合需要即時連續流的場景。
  • HTTP POST(http://:簡單、無狀態,適合往一個 REST 端點丟資料。
  • Web Serial(USB):透過 Chrome 原生 navigator.serial,用 9600/115200 等鮑率直接走有線 USB 串口——不依賴 Wi-Fi,延遲最穩。

光有資料還不夠,使用者得知道「到底有沒有在送」。所以面板上即時顯示連線狀態指示燈、已傳封包數、上傳頻寬(KB/s)與除錯日誌——把看不見的資料流變成看得見的儀表。

最惱人的攔路虎:Mixed Content

這個功能最大的技術障礙,是一個很多人踩過的坑:Mixed Content(混合內容)

LocalPapa 以 HTTPS 載入。但當你想把手機數據送到區網內的電腦(例如 192.168.42.x)時,那通常是個 不安全的 http://ws:// 位址。瀏覽器的安全策略會直接攔截「HTTPS 頁面發出的非安全請求」——於是資料根本送不出去。

這不是 bug,是瀏覽器刻意的安全設計。我們的解法是務實的兩手:

  1. 在 UI 裡清楚說明怎麼在瀏覽器設定中為本頁解除 Mixed Content 限制。
  2. 提供一個 http:// 版的本工具網址作為備選——直接用 HTTP 載入頁面,就不存在「HTTPS→HTTP」的混合問題,降低測試門檻。
與其假裝問題不存在,不如把限制攤開、給出可操作的繞道。誠實面對平台的安全邊界,本身就是工具該做的事。

接收端不能只給規格——直接給程式

很多「串流工具」會把資料送出去就算完,剩下「你自己想辦法接」。但那等於把一半的難題丟給使用者。

所以我們內建一段可一鍵複製的 Python 接收伺服器腳本,同時支援 WebSocket 與 HTTP POST 兩種協定。複製、貼上、執行——電腦端立刻就能收到手機的資料流,開箱即用。把「最後一哩」也鋪好,工具才算真的可用。

連線要連得乾淨,也要斷得乾淨

串流工具最容易忽略的是收尾。Web Serial 尤其如此:

  • 連接前用 requestPort() 讓使用者選串口(同樣必須在使用者手勢內)。
  • 停止串流時,確實呼叫 writer.releaseLock() 釋放寫入鎖,再 port.close() 關閉連接。

少了這步,串口可能被鎖住、下次連不上。「開乾淨」容易,「關乾淨」才是穩定的關鍵。

此外也用 localStorage 記住使用者的協定、目標網址與頻率設定,下次開頁不必重填。

後來還加了 GPS 與磁力計

這條線後續又補上 GPS 定位navigator.geolocation.watchPosition)與磁力計new Magnetometer())。磁力計在 iOS 沒有原生支援,於是面板上以紅色退路文字提示、並導向用指南針方位代替——又是一次「平台差異要好好善後」的實例。CSV 匯出與 JSON 串流也同步補齊這些新欄位,並加上單元測試確保序列化結構正確。

學到的事

  • 資料要送得出去,也要有人接得住。 附上接收腳本,才把工具從「半成品」變「能用」。
  • 平台的安全限制不能硬幹,要繞得漂亮。 Mixed Content 擋住你時,正確答案是說明+備選路徑,不是叫使用者關掉所有保護。
  • 資源要關乾淨。 releaseLock + close 這種收尾,決定了工具用第二次、第三次還順不順。
  • 隱私在串流場景依然成立。 關鍵是:端點是你自己選的,資料流向你自己的電腦,從頭到尾沒有經過我們的伺服器。

一以貫之的問題

從讀感測器、到串流感測器,這條線把系列的問題又推進一步:瀏覽器不只能讀硬體,還能把它變成一個即時、可程式化的資料源——而且全程由使用者掌控。

一支手機 + 一個網頁,就是一台感測器。答案,又一次是:可以。

下一篇開發紀錄,我們會走到一個全新領域——把 LLM 產生的 Mermaid 文字一鍵變成圖。想追蹤系列?把 LocalPapa Notes 加入書籤吧。

Dev Log: Turning a Phone into a Live Sensor Data Source

This is the seventh post in the LocalPapa Notes dev-log series. Last time we built a phone sensor dashboard that shows the level, compass and G-force live on screen. But a next thought arrived quickly: if that data could be sent to a computer in real time, wouldn't the phone become a ready-made sensor data source?

So the tool grew a "streaming" ability.

From "viewing" to "sending"

The original dashboard was for humans to look at. The streaming feature is for machines to use: it packs the accelerometer, gyroscope, compass, gravity and G-force readings into JSON at a fixed rate and sends them live to an endpoint you specify.

You can send 10, 30 or 60 times per second (10/30/60Hz), emitted steadily via setInterval. A single phone instantly becomes a low-cost IMU (inertial measurement unit) data source — useful for machine-learning data collection, robotics telemetry, or interactive installations.

Three transports, for different scenarios

We offer three ways to ship the data out:

  • WebSocket (ws://): low-latency, bidirectional, for scenarios needing a continuous live stream.
  • HTTP POST (http://): simple, stateless, for tossing data at a REST endpoint.
  • Web Serial (USB): via Chrome's native navigator.serial, at baud rates like 9600/115200, straight over a wired USB serial port — no Wi-Fi dependency, the most stable latency.

Data alone isn't enough; the user needs to know "is it actually sending?" So the panel shows a live connection-status light, packets sent, upload bandwidth (KB/s) and a debug log — turning an invisible data flow into a visible gauge.

The most maddening obstacle: Mixed Content

The biggest technical hurdle here is a pit many have fallen into: Mixed Content.

LocalPapa loads over HTTPS. But when you want to send phone data to a computer on your LAN (say 192.168.42.x), that's usually an insecure http:// or ws:// address. The browser's security policy blocks outright an "insecure request made from an HTTPS page" — so the data simply never leaves.

This isn't a bug; it's deliberate browser security. Our fix is a pragmatic two-pronged one:

  1. Explain clearly in the UI how to lift the Mixed Content restriction for this page in browser settings.
  2. Provide an http:// version of the tool's URL as an alternative — load the page over plain HTTP and the "HTTPS→HTTP" mixing problem disappears, lowering the testing barrier.
Rather than pretend the problem doesn't exist, lay the restriction bare and offer an actionable detour. Honestly facing the platform's security boundary is itself part of a tool's job.

Don't just hand over a spec for the receiver — hand over the code

Many "streaming tools" stop once they've sent the data, leaving "you figure out how to receive it" to the user. That dumps half the problem on them.

So we built in a one-click-copy Python receiving server script, supporting both WebSocket and HTTP POST. Copy, paste, run — and the computer immediately receives the phone's data stream, out of the box. Paving that "last mile" is what makes a tool actually usable.

Connect cleanly — and disconnect cleanly too

The thing streaming tools most often neglect is teardown. Web Serial especially:

  • Before connecting, use requestPort() to let the user pick a port (again, must be within a user gesture).
  • When stopping, actually call writer.releaseLock() to release the write lock, then port.close() to close the connection.

Skip that, and the port can stay locked and fail to connect next time. Opening cleanly is easy; closing cleanly is the key to stability.

We also use localStorage to remember the user's protocol, target URL and frequency, so they needn't re-enter them next visit.

GPS and a magnetometer came later

This line later gained GPS (navigator.geolocation.watchPosition) and a magnetometer (new Magnetometer()). The magnetometer isn't natively supported on iOS, so the panel shows a red fallback note and points users to the compass heading instead — another instance of "platform differences need proper aftercare." CSV export and the JSON stream gained these new fields too, with unit tests added to keep the serialization structure correct.

What we learned

  • **Data has to be sendable and receivable.** Shipping a receiver script is what turns the tool from "half-built" into "usable."
  • You can't brute-force platform security — you route around it elegantly. When Mixed Content blocks you, the right answer is explanation plus an alternative path, not telling users to disable all protection.
  • Close your resources cleanly. Teardown like releaseLock + close decides whether the tool still works smoothly the second and third time.
  • Privacy still holds in a streaming scenario. The key: the endpoint is yours, the data flows to your own computer, and it never passes through our servers at any point.

The same question, one step further

From reading sensors to streaming them, this line advances the series' question again: a browser can not only read hardware, but turn it into a real-time, programmable data source — entirely under the user's control.

One phone plus one web page is a sensor. The answer, once again: yes.

In the next dev log, we move into fresh territory — turning LLM-generated Mermaid text into a diagram with one click. Want to follow the series? Bookmark LocalPapa Notes.

探索 39+ 個隱私優先的瀏覽器工具
全程本地運算、檔案不上傳。
前往 LocalPapa →
Explore 39+ privacy-first browser tools
Everything runs locally — your files never leave your device.
Visit LocalPapa →

想看英文版?點右上角 EN 切換語言。

Prefer Chinese? Tap at the top-right to switch.