装置の前まで歩かないと設定を変えられない
小型の組み込み機器には、2行程度のキャラクタLCDと数個の物理ボタンしかないものがあります。
装置の前に立ってIPアドレスを確認する。起動したかを見る。致命的な異常が出ていないか確かめる。その程度なら、2行LCDはとても頼りになります。ネットワークへ入れないときにも使えるため、最後の確認手段として残しておきたいUIです。
一方で、設定項目が増えてくると事情が変わります。階層の深いメニューを物理ボタンで行き来し、短い項目名を読み解きながら値を変更するのは、作る側にも使う側にも負担でした。
そこで当時の装置では、LCDをなくすのではなく、日常操作をWebブラウザへ逃がしました。
- LCDではIPアドレス、起動状態、異常を確認する
- ブラウザでは装置状態の監視、設定変更、通常操作を行う
全体の姿は次のとおりです。装置は箱にLCDと操作ボタンを持ち、背面のLANポートで社内LANへつながります。装置のIPアドレスはLCDに出るので、ブラウザにはそれを打ちます。離れた席のPCからは、ブラウザで同じ装置を触ります。操作の入口は2つですが、書き換わる状態はひとつです。
この2つの経路が同じ状態を見ているという前提が、以降の話の土台になります。画面へ「自分が送った値」ではなく「装置が返した値」を出す理由も、ここから来ています。
本記事では、LinuxカーネルとBusyBox中心のrootfsへlighttpdとPHPを載せ、ブラウザのjQuery/AjaxからC製コンソールプログラムを呼ぶ構成を、公開用の小さなサンプルで再構成します。
この記事で分かること
- BusyBox中心の小型Linux装置へWeb UIを足すときの役割分担
- jQuery、Ajax、lighttpd、php-cgi、PHP、C製プログラムのつながり
- AjaxからCを直接呼ぶのではなく、PHPを境界にする理由
- LCDとWeb UIを競合させず、同じ装置制御機能へつなぐ考え方
- 公開サンプルを通常のLinux上で動かす手順
この記事の前提
- 対象読者:C言語とLinuxの基礎が分かり、小型装置へ管理画面を載せたい方
- 前提知識:Cのコンソールプログラム、HTTP、HTML/JavaScriptの基礎
- 対象範囲:信頼できるローカルネットワーク内の教材用構成
- 扱わない範囲:認証、TLS、CSRF対策、インターネット公開、ハードリアルタイム制御
- 経験時点:Intel SoC向けのPoky系Linux、lighttpd、php-cgi、PHP 5.6.38を使った装置開発
- 公開サンプル:特定製品のソースではなく、記事用に新規作成した模擬装置
公開サンプルには認証やTLSがありません。そのまま製品へ組み込んだり、インターネットへ公開したりしないでください。
BusyBoxは「小さなOS」そのものではない
会話では「BusyBox Linuxを入れる」と表現することがありますが、厳密にはBusyBoxだけでOSが完成するわけではありません。
今回の装置は、次のような組み合わせを想定しています。
- Linuxカーネル
- BusyBoxを中心にした小さなユーザーランド
- 起動スクリプトと設定ファイル
- lighttpd
- php-cgi
- 装置制御用のCプログラム
- HTML、CSS、JavaScript、PHPからなるWeb UI
Yocto Projectなどでrootfsを作る場合も、これらを必要なパッケージとして組み込むことになります。
LCDとWeb UIは競合ではなく、役割分担
Web UIを載せたからといって、LCDが不要になるわけではありません。
| 状況 | LCD | Web UI |
|---|---|---|
| 起動直後 | 起動段階を表示 | Webサーバ起動前は使えない |
| IPアドレス確認 | 装置単体で確認できる | 接続先が分からないと開けない |
| ネットワーク障害 | 最後の確認手段になる | 接続できない |
| 日常操作 | 複雑な操作には不向き | 一覧表示、入力、説明を置きやすい |
| 状態監視 | 2行に収まる範囲 | 複数の値や履歴をまとめられる |
| 致命的な異常 | 装置の前で確認できる | Web処理自体が止まる場合がある |
LCDは保守用、Web UIは日常操作用。この分け方にすると、2行LCDへすべての設定画面を押し込まずに済みます。
ブラウザから装置までの経路
ブラウザから見ると、ボタンを押して装置が動くだけです。しかし内部では、次の層を通ります。
- jQueryが画面操作を受ける
- AjaxがHTTPリクエストを送る
- lighttpdがPHPへ処理を渡す
- PHPが操作名と値を検証する
- PHPの
exec()がC製コンソールプログラムを起動する - Cプログラムが装置固有の処理を行う
- 終了コードとJSONをPHP経由でブラウザへ返す
AjaxやjQueryがCプログラムを直接起動するわけではありません。ブラウザには、装置上のプロセスを直接実行する権限がないためです。
本記事で示すコードは、当時の製品ソースではなく、記事用に新規設計した公開サンプルです。当時の記録から確認できているのは、Poky系Linux、lighttpd、php-cgi、PHP 5.6.38という土台と、AjaxでPHPへ要求を送っていたことまでで、PHPからC側を起動する部分の実装詳細は、この記事の設計として作り直しています。以降の説明は、すべて公開サンプルの実装に沿って読んでください。
公開サンプルで動きを確認する
公開用サンプルはGitHubに公開しています。→ embedded-web-control-demo
構成は次のとおりです。
-
native/device_ctl.c:装置制御を担当するC製コンソールプログラム -
www/api/device.php:HTTPの値を検証してCを呼ぶPHP -
www/index.html、www/app.js、www/style.css:jQueryとAjaxのWeb UI -
deploy/lighttpd.conf.example:lighttpdとphp-cgiの設定例 -
Makefile:ビルドと単体確認
実ハードウェアの代わりに、/tmp配下の状態ファイルを模擬装置として使います。ブラウザからSTART、STOP、0~100のレベル設定を行えます。
通常のLinuxで試す
必要なのは、C11コンパイラ、make、PHP、curlまたはwgetです。
make setup
make test
php -S 127.0.0.1:8080 -t www
ブラウザでhttp://127.0.0.1:8080/を開きます。
最初はPHP内蔵サーバで構いません。C、PHP、Ajaxの経路が動くことを確認してから、lighttpdへ切り替えます。これなら「lighttpd設定の問題」と「アプリケーションの問題」を分けて確認できます。
C製コマンドを単体で試す
Web UIより先に、装置制御プログラムを単体で確認します。
./bin/device_ctl status
./bin/device_ctl set-level 42
./bin/device_ctl start
./bin/device_ctl stop
正常時はJSONを標準出力へ返します。
{"ok":true,"running":false,"level":42,"updated_at":1785945600}
単体で動かないCプログラムをPHPから呼んでも、原因の切り分けが難しくなるだけです。下から順に確認するのが、この構成では一番早い方法です。
PHPは装置操作の翻訳役に限定する
公開サンプルのPHPは、次の4操作しか受け付けません。
statusstartstopset_level
set_levelの値は0~100の整数へ制限します。Ajaxから受け取った文字列を、そのままシェルコマンドへ連結する入口は作りません。
HTTPのaction名はset_level、C側のサブコマンドはset-levelで、表記が異なります。誤記ではなく、PHPが「HTTPの語彙」を「装置制御コマンドの語彙」へ翻訳していることを示しています。PHPは検証済みの整数をset-levelという固定の文字列へ組み立て直してから渡します。
if ($action === 'status') {
$command .= ' status';
} elseif ($action === 'start') {
$command .= ' start';
} elseif ($action === 'stop') {
$command .= ' stop';
} elseif ($action === 'set_level') {
/* 0~100の整数を検証してから固定コマンドへ追加 */
} else {
fail_response(400, 'unknown action');
}
PHPへ装置固有のレジスタ操作を書き始めると、Web UIとハードウェアが密結合になります。PHPはHTTPの値を検証し、C側が理解する装置機能へ翻訳するところまでにします。
公開サンプルでは、状態取得のstatusだけをGET、状態を変更するstart/stop/set_levelをPOSTに制限しています。JavaScript側でメソッドを使い分けるだけでなく、PHP側でも一致しない要求を405 Method Not Allowedとして拒否します。
C製コマンドを残した理由
C側をコンソールプログラムとして作っておくと、Web UIがなくてもSSHやシリアルコンソールから動作確認できます。
- 単体試験しやすい
- 終了コードで成功と失敗を返せる
- 標準出力をPHPから取得できる
- Web UI以外の保守ツールからも利用できる
- PHPを変更せず、装置固有処理だけを差し替えられる
反面、Ajax要求のたびにプロセスを起動します。1秒程度の状態ポーリングや、人がボタンを押す頻度なら構成が単純という利点がありますが、高頻度の取得や長時間かかる制御には向きません。
その境界を越えたら、Cプログラムを常駐デーモンにし、PHPとはUnixドメインソケットなどで通信する構成を検討します。最初から常駐化するのではなく、必要になった時点で変える方が、小型装置では扱いやすい場合もあります。
Ajaxでリアルタイムっぽく見せる
公開サンプルでは、1秒ごとにstatusを取得します。STARTやレベル変更を送った直後は、応答を画面へ反映し、続けて状態を再取得します。
これは厳密なリアルタイム制御ではありません。
ブラウザ、PHPプロセス、Cプログラム、ドライバ、専用ハードウェアは、それぞれ異なる速度で動きます。短い周期で状態を確認し、操作中と失敗を明示することで、ユーザーから見た一貫性を作っています。
LCDの記事で扱った「異なる速度で動くものを、ユーザーから見て一貫しているように見せる」という考え方は、Web UIでも同じです。
詳しいポーリング周期、古い値の扱い、操作中表示は第2回で扱います。
実機へ置き換える場所
サンプルでは、Cプログラムが状態ファイルを読み書きします。実機へ移すときは、この部分だけを次の処理へ置き換えます。
- デバイスファイルの
read/write ioctl- FPGA/ASICのレジスタ操作
- 別CPUや制御プロセスとの通信
Web側へレジスタアドレスを見せるのではなく、start、stop、set-levelのような装置機能の言葉を保ちます。
これは以前のLCD記事で扱ったDeviceControlPortと同じ考え方です。LCDとWeb UIが別々に専用ハードウェアを触るのではなく、同じ装置制御境界を利用します。
この構成をそのまま製品へ載せない
教材サンプルは、層のつながりを確認することへ絞っています。製品へ載せるなら、少なくとも次が必要です。
- 認証とセッション管理
- TLSまたは信頼できる管理ネットワークへの限定
- CSRF対策
- 操作権限
- 監査ログ
- C処理のタイムアウト
- 複数ブラウザとLCD操作の排他
- Webサーバ実行ユーザーの最小権限化
- エラー内容をそのままブラウザへ返さない処理
また、sudoersへ広い権限を与えて解決するのは避けます。必要なデバイスだけを専用グループへ許可する、あるいは権限を持つ常駐デーモンへ操作を限定する方が、境界を説明しやすくなります。
確認した範囲
確認日は2026-08-06です。環境はLinux(Ubuntu 24.04ベース)、PHP 8.4.21の内蔵サーバ、Chromium 141.0.7390.37のヘッドレスモード、gcc 13.3.0です。
lighttpd+php-cgi構成、組み込み実機(BusyBox中心のrootfs)、jQuery 3.7.1本体は未確認です。ブラウザ側の確認には最小のjQuery互換シムを使っています。
まとめ
- LCDはIPアドレス、起動、異常を確認する保守用UIとして残します。
- 日常的な監視と操作はWeb UIへ分けます。
- jQuery/AjaxはPHPへHTTP要求を送り、PHPが許可済みのC製コマンドを呼びます。
- PHPは入力検証と翻訳、Cは装置固有処理を担当します。
- Ajaxポーリングはリアルタイム制御ではなく、ユーザーへ一貫した操作感を返すための仕組みです。
- 公開サンプルは通常のLinuxで確認してから、BusyBox中心のrootfsへ持ち込めます。
次回は、状態取得を繰り返す周期、操作直後の表示、通信失敗時の古い値など、「リアルタイムではないものをリアルタイムに感じさせる」部分へ踏み込みます。


