目次を開く

手元から操作。家電には赤外線で届く。

SH-02Eを、操作したい家電に赤外線が届く位置に置く。PCやスマホのWeb画面・API、またはAlexaから指示すると、Wi-Fi経由で指示を受けたSH-02Eがリモコンの赤外線信号を送り、照明やエアコンを操作する。

PC・スマホ・Alexa↓ Linuxサーバーで中継(Home Assistant・API)Wi-FiにつないだSH-02E↓ 内蔵LEDから赤外線を送信照明・エアコンなどの家電

たとえば、スマホのボタンで照明を点灯・消灯したり、常夜灯に切り替えたり、エアコンを操作したりできる。Alexaの定型アクションを設定すれば、「ライトを常夜灯にして」のような言い方にも対応できる。

以下では、この構成を実機で動かすまでの仕組みと手順を紹介する。家電ごとの赤外線信号の登録、接続を維持する方法、Web UI・Alexaとの連携も扱う。

PRACTICAL GUIDES

具体的な手順から読む

環境準備からAPI・Web UI、日常の切り分けまで。実行場所と開始条件を確認しながら進められる4つの詳細ページ。

01

何ができたのか

SHARP SH-02Eの内蔵赤外線LEDで、照明とエアコンを操作できた。成功したのは、純正のIrDA UARTで家電用信号を送る方法ではなく、専用のカーネルモジュールでGPIOを駆動する方法だった。

アプリからの直接操作に加え、Linuxサーバーを中継し、Home AssistantとAlexaアプリから操作する経路も作った。通常の送信に外付け赤外線Hubは必要ない。

項目確認したこと
照明の操作2種類の照明で点灯・消灯。CL-RGPの11ボタンは使用者が実機動作を確認。
エアコンの操作三菱144-bit形式を実装し、端末アプリからの操作成功を確認。全温度・全モードの組合せは未検証。
遠隔操作Home Assistant経由の照明操作と、Alexaアプリからの照明のオン・オフを確認。
接続維持画面消灯中の待機・受信と、受信終了後の復帰を確認。長期連続運用は未検証。
確認していないこと光学的な搬送波測定、到達距離の定量評価、再起動を挟む完全自動復旧、Echoの音声操作の全経路。

02

対象と、再現前に押さえること

対象この実験の環境
端末 / OSSHARP SH-02E / Android 4.1.2(API 16)
ビルド / カーネル02.00.06 / Linux 3.4.0 / APQ8064
アプリ / 送信モジュール実験アプリ v4.4 / ドライバAPI 3
ビルド環境JDK 11、Android SDK build-tools 34.0.0、NDK 23.1.7779620、ARM EABI 4.6系ツールチェーン
遠隔操作側Linux、ADB、Home Assistant 2026.9.4

この端末では、Androidアプリから直接使える家電リモコン用の汎用送信経路を確立できなかった。実験では一時的な特権操作でモジュールをロードした。boot / recoveryの書き換え、永続root、suの導入は行っていない。

モジュールは再起動で消える。アプリを残しておくだけでは送信を再開できないため、復帰時はUSB接続で初期化できる作業環境を確保する。別ビルドへの移植では、機種名のチェックを外すだけでなく、カーネル構成・シンボル・メモリ配置の再調査が必要になる。

端末とネットワークの扱い

日常用の個人データが入っていない実験端末を使う。旧AndroidのWi-Fi ADBと家電操作APIは信頼するLAN内に限定し、インターネットへ公開しない。長時間給電する場合は端末・バッテリーの状態も確認する。

03

送信経路を分けて考える

操作Home Assistant / Alexa
↓
中継Linux + Wi-Fi ADB
↓
端末内Androidアプリ → JNI → カーネル
↓
物理信号GPIO → 内蔵IR LED → 家電

端末アプリを直接使う場合、LinuxサーバーとWi-Fiは不要。JavaのワーカースレッドからJNIを呼び、専用デバイスへ送信コマンドを書き込む。遠隔操作は、この同じ送信処理への入口として後から追加した。

端子・デバイス役割
GPIO 10IR送信。純正UART機能から送信中だけGPIO出力へ切り替える。
GPIO 11IR受信。送信とは別に扱う。
GPIO 81トランシーバのshutdown。Lowで有効、Highで停止。
/dev/msm_shirda純正ドライバのゲート。open / closeによる有効化・終了処理を利用する。
/dev/ttyHS0純正IrDA UART経路。成功した方式の送信には使わない。
/dev/sh02e_ir今回追加した送信用デバイス。状態読み出しは送信を伴わない。

送信前の端子設定と出力値を保存し、フレーム終了時にUART設定へ戻す。送信全体の終了時にはshutdownと純正ゲートを閉じる。正常終了だけでなく、タイムアウト時にも復元することが重要だった。

04

発光と「有効な信号」は違う

スマートフォンのカメラで発光が見え、Hubの学習が成功しても、家電が反応するとは限らない。実際、UARTを使った方式は発光と学習受付まで確認できたが、照明は動かなかった。

赤外線リモコンは、搬送波を出す区間(mark)と止める区間(space)を組み合わせる。搬送波の周波数だけでなく、dutyと区間の長さを揃える必要がある。

NEC / DATA BIT

0と1は、休む長さで変わる。

ビット0:mark 562µs、space 562µs。説明用の概念波形mark 562 µsspace 562 µs
説明用の概念図。実測波形ではなく、描いた搬送波の本数も実装値とは異なります。
パラメータNEC形式で使った値
搬送波38 kHz、duty 約1/3
leadermark 9,000 µs + space 4,500 µs
bit 0mark 562 µs + space 562 µs
bit 1mark 562 µs + space 1,687 µs
footermark 562 µs
データ順32 bit。各バイト内はLSB first。コマンドと反転値を検証。
繰り返しこの実装はフルフレームを3回。短いNEC repeat frameではない。

同じフレームの3回送信はこの照明で動いた設定であり、すべての家電への推奨値ではない。明るさの相対変更やタイマーでは、反復が複数回の操作として解釈される可能性を確認する。

05

時間精度はカーネル内で確保する

userspaceのsleepやUARTの送信完了待ちでは、mark長のばらつきを抑えられなかった。成功した実装ではAPQ8064のCPU0 DGTを時間基準にし、GPIOをカーネル内で切り替える。

PSEUDOCODE
# 搬送波の予定時刻を計算する概念式(実行コードではない)
rise(n) = start + n * 6_750_000 / 38_000
fall(n) = rise(n) + 6_750_000 / (38_000 * 3)

# 前の処理から相対的に待つのではなく、同じ基準からの絶対期限を待つ
wait_until(rise(n))
set_tx_high()
wait_until(fall(n))
set_tx_low()

相対待機を積み上げず、各エッジの絶対期限を計算することで誤差の蓄積を避けた。タイマーdividerと一定時間の進行量も検査する。ただし、これはタイマー停止や大きな設定違いの検出であり、光の周波数を校正するものではない。

このビルドで調べたレジスタ配置
対象位置
GPIO MMIO物理 0x00801000、長さ 0x1000
TX設定 / データページ内 0xa0 / 0xa4
shutdown設定 / データページ内 0x510 / 0x514
CPU0 DGT物理 0x0208a000、長さ 0x1000
COUNT / ENABLE / DIVページ内 0x28 / 0x2c / 0x34

build 02.00.06での調査値。別機種・別ビルドでの書き込み先として流用しない。

NEC送信では、フレーム内でローカル割り込みを約67 ms停止した。対応する入力範囲で約76 ms以内に抑えているが、システム応答に影響する実験実装である。遅延超過やタイマー停止を検出すると中止し、端子を復元する。

長いエアコン信号では、フレーム全体の割り込み禁止を避け、mark中だけ禁止した(最大3.4 ms)。space中は許可し、次のmark開始が80 µsを超えて遅れたら中止する。フレーム内のpreemptionは停止するため、この方式も負荷のない処理ではない。

測定値の読み方

初期NEC試験では最後のフレームが約66.90 ms、予定時刻に対する最大遅延が約1.19〜1.33 µsだった。ただし記録点はGPIO書き込み前。IR LEDの光のエッジを直接測定した値ではない。

06

純正カーネルへモジュールを載せる

純正カーネルはモジュールを有効にしていたが、独自のSHA-256許可チェックがあり、root権限だけでは自作モジュールをロードできなかった。公開ソースの許可値と実機の値にも違いがあった。

実験では、実機由来の純正モジュールとカーネルImageを照合し、RAM上の許可値を自作モジュールのハッシュへ一時変更した。ロード後は元の値へ戻して一致を確認する。許可チェック全体を無効化したわけではない。

  1. 実機のビルドを確定する。端末情報、カーネル構成、純正バイナリを照合する。
  2. ビルド条件を合わせる。同じ構成とModule.symversを使い、module_layoutのCRCを確認する。
  3. 無送信のprobeから始める。GPIOを駆動する前に、ロード・アンロードだけを検証する。
  4. 変更と復元を一組で設計する。変更前・変更後・復元後の内容を照合し、失敗時は後続処理を止める。
  5. アプリだけに送信権限を渡す。インストール後の実UIDを取得し、ノードのグループとモードを設定する。
特権操作の調査で得た注意点

既存のframebuffer mmapの32-bit加算overflowを利用したRAM mapperと、Dirty COWを用いた一時的なrun-as置換を使った。いずれも、この古い環境に固有の実験手段であり、新しいAndroidへそのまま適用できない。

  • ARM ELFのstack属性を明示する。実行属性の不足は意図しないマッピングに影響した。
  • entry付近には動的リンカが書き換える領域があった。コードや文字列を重ねない。
  • 未リンクのオブジェクトから機械語だけを切り出さない。未解決のARM再配置が自己分岐とタイムアウトを招いた。
  • run-asは一部分だけでなく、最後にファイル全体のハッシュで復元を検証した。

過去の試行では端末フリーズも起きた。専用の初期化ツールは固定パスや個体照合を含み、移植用として整理できていないため、このページでそのまま実行する手順やバイナリは提供しない。

07

赤外線コードを取り込む

信号源には物理リモコンと、正常に家電を操作できる学習Hubを使った。Hubはあくまで参照送信源で、完成した端末からの送信には関与しない。

  1. 受信を先に開始する。受信待ちを十分長く設け、ユーザーの「押し終わった」で終了できるようにする。
  2. 一括で採るなら、順番と間隔を決める。ボタン内の反復とボタン間の無信号区間を区別する。
  3. 完全なフレーム同士を比較する。同じ完全フレームが2件以上あること、コマンド反転値やチェックサムが一致することを確認する。
  4. 足りないボタンだけを再採取する。欠けたbitを推測で埋めず、不一致や不完全データは登録しない。
  5. 最後は実際の家電で確認する。デコード成功・送信成功・家電の反応を別の記録として残す。

一括採取では、ある記録のボタン内最大間隔1.150秒、ボタン間最小間隔2.138秒に対し、2秒を境界にすると区間と指示した順番が一致した。この値はその記録に対する選択で、固定の万能値ではない。

UART受信の限界

UARTのread単位やchunk境界は、光のパルス幅そのものではない。長いエアコン信号ではmarkの結合や取りこぼしがあり、UARTだけで安定した完全フレームが得られなかった。

そこで受信GPIOの立ち下がり割り込みをDGTで記録する実験モジュールを用いた。受信中だけ端子を切り替え、短時間で終了・復元する。PM_QOS_CPU_DMA_LATENCY=0を受信中だけ要求した試行で改善したが、省電力復帰遅延だけが原因とは断定していない。受信モジュールは日常の送信には不要。

08

照明とエアコンで違うこと

照明:ボタンを送る

CL-RGPでは、明るく・暗く・白色・暖色・メモリ1・メモリ2・おやすみ・点灯/全灯・常夜灯・消灯・選択の11操作を登録した。家庭固有の操作名やコード一覧は省き、プロトコルと設計上の違いを示す。

「点灯/全灯」は独立した絶対的な全灯指示とは限らず、物理ボタンと同じ切り替え動作になる。メモリ呼び出しと長押し保存も別の操作である。今回メモリは呼び出しのみ、光源選択は専用コードだけに長押し相当の21フレーム送信を許可した。

エアコン:設定全体を送る

三菱144-bit形式では18 bytesの状態を送った。冷房26℃・27℃、暖房24℃、停止の参照フレームを採取し、生成値と照合した。送信は2フレーム。

PROTOCOL NOTE
# この三菱144-bit形式での温度生成の要点
# すべての三菱エアコンに共通する仕様ではない
16 <= temperature <= 31
byte7 の温度領域 = temperature - 16
0.5 ℃を示すbitをクリア
最後のbyte = sum(先頭17 bytes) & 0xff

形式の照合にはIRremoteESP8266の三菱実装も参照した。風量・風向などは取得したテンプレートの設定を維持しており、独立した変更機能は実装していない。生成値が正しいことと、全設定で家電が動くことは別の検証になる。

09

Androidアプリと接続維持

機器ごとの信号と操作名をデータに分離し、Java → JNI → 送信モジュールの共通経路で処理する。通信成功を家電の状態検知と混同しないよう、画面には送信結果として表示する。

端末アプリ自体にはネットワーク待受を追加しなかった。遠隔操作はADBから制限付きBroadcastReceiverを呼び、受理する操作とパラメータを検証する。アプリはAndroid 4.1で扱えるv1署名を有効にし、Java/D8/JNIをAPI 16向けにビルドした。実験環境ではJDK 21でD8が失敗し、JDK 11で成功した。

画面を消すと接続が途切れた理由

信号採取のために待機サービスを停止したところ、CPUとWi-Fiのロックも解放され、画面消灯中のADB接続が切れた。修正は「送信の許可」と「接続の維持」を分けることだった。

常時維持するもの

foreground service、CPUのPartial WakeLock、Wi-FiのWifiLock。旧Androidで画面消灯中も待機を続けるために使用。

受信中だけ止めるもの

ネイティブ送信処理へのアクセス。識別子付きの取得モードで拒否し、受信後に権限と端子を復元して解除。

アプリ更新後はMY_PACKAGE_REPLACEDで、以前有効だった待機状態を再開する。これは端末再起動後のカーネルモジュール復旧とは別である。長時間待機の消費電力は定量評価していない。PowerManagerとWifiLockの仕様も参照。

10

LinuxとHome Assistantで遠隔化する

中継サーバーは、許可した機器・操作だけを受け付け、Wi-Fi ADBでアプリへ指示を渡す。APIはループバックで待ち受け、Home Assistantから認証付きで呼び出す。端末やサーバーのIPはDHCP予約などで安定させる。

SHELL · 値の置換が必要
# USB接続中にWi-Fi ADBを有効化する例
# 値は自分の端末に置き換える
adb devices -l
adb -s <DEVICE_SERIAL> tcpip 5555
adb connect <PHONE_LAN_IP>:5555

# この実験モジュールが初期化済みの場合の無送信診断
adb -s <PHONE_LAN_IP>:5555 shell cat /dev/sh02e_ir

中継側は送信を直列化し、失敗時に自動再送やキューへの蓄積をしない。応答が途切れたときは「送信されていない」のか「送信後の応答だけ失われた」のか判別できず、再送で操作が二重になるからだ。接続確認側でのADB再接続と、赤外線送信側の再試行は分ける。

ADBプロセスの終了コードだけで成功判定せず、アプリからの明示的な200 / OKを検証した。それでも家電の受信確認ではない。Home Assistantのオン表示も、基本的には最後に送った指示に基づく記録になる。

11

HTTP APIからSH-02Eを操作する

Linux側の中継APIはGET /healthとPOST /sendを提供する。SH-02EのIPに直接HTTPを送る方式ではなく、サーバーがWi-Fi ADBでアプリを呼び出す。

認証、curlの送信例、エラーの意味、Home Assistant REST APIの呼び方は詳細手順にまとめた。

APIの具体的な手順 →

12

Web UIをリモコンにする

Home Assistantのブラウザー画面から照明を操作できる。常夜灯やメモリ呼び出しは、状態を持つトグルよりも1回実行するボタンとして扱うと分かりやすい。

詳細手順ではダウンロードできる設定例を使い、値の設定・構文検証・起動・実機確認まで説明する。自作Web UIを作る場合は、ブラウザーと中継APIの間に認証を管理するバックエンドを置く。

Web UIの構築手順 →

13

SH-02E自身にHTTPサーバーを載せるなら

SH-02E内にHTTPサーバーを組み込むことも設計できるが、今回のアプリでは未実装。API16対応のHTTP実装、認証、入力検証、送信排他、foreground serviceとCPU / Wi-Fiロックが必要になる。HTTP入口を作っても、カーネルモジュールの初期化は別途必要。

拡張案と制約を読む →

14

Alexa連携の落とし穴

Home AssistantのEmulated Hueで照明操作を公開し、Alexaアプリから操作できた。追加時はPhilips Hue V1(丸型)の検出経路を使い、LAN内の80番ポートで受けた。環境やEcho世代による互換性は公式ドキュメントを確認する。

「常夜灯」を電源スイッチにしない

常夜灯、メモリ呼び出し、消灯は、一度実行する操作だ。「常夜灯オフ=消灯」と割り当てると、次に操作するためのリセットと消灯が混ざってしまう。

そこで操作用の機器は、オンで1回送信し、サーバー上で約1秒後にオフへ戻す形にした。このオフは待機へ戻すだけで、赤外線を送らない。通常の照明のオン・オフは別の機器として残す。

「照明を常夜灯にして」のような言い方には、Alexaの定型アクションで音声フレーズを登録し、対応する操作用機器の電源オンを割り当てる。毎回オンを送る入口として使う設計で、アプリのトグル表示に依存しない。音声経路の実機確認は別途必要。

この構成で公開したエアコンはオン・オフのみ。温度・モードが自動でAlexaに現れるわけではない。また、SwitchBotのHubを信号源に使っても、SH-02EがSwitchBot公式アプリへHubとして登録されるわけではない。

15

失敗から残った知見

試したこと得られたこと
UART経由の擬似搬送波発光しても家電は無反応。波形の適合と発光の有無は別。
反復回数を大量に増やす不適切な波形は回数だけでは改善しなかった。
tcdrain・FIFO優先度・nice調整UART DMAやスケジューリングを含む経路の揺れを解消できなかった。
Hubでの学習成功を判定にする学習した信号の再生でも照明が動かなかった。成功の基準は家電の反応。
短い受信窓で1ボタンずつ案内利用者が押すタイミングに間に合わない。受信を先に開始し、一括採取と明示終了へ変更。
受信中に待機サービスを停止CPU / Wi-Fiロックが消え、接続も切れた。受信と接続維持を分離。
Alexa用に操作をオン・オフ機器化操作ボタンと状態表示が混同された。無送信リセットと表示上の制約を明示。

切り分けは、入口から順に

  1. Alexa / Home Assistantが、正しい操作をサーバーへ渡したか。
  2. 中継の準備確認とADB接続は正常か。
  3. アプリが処理を受理し、モジュールが送信を完了したか。
  4. 端末の赤外線ポートが家電へ向き、遮蔽物がないか。
  5. 正しい家電の信号か。家電そのものが反応したか。

「Alexaが『はい』と言った」「APIが200を返した」「カメラで発光した」のどれも、最後の確認の代わりにはならない。

16

再現するなら、この順番で

  1. 同じ端末・ビルドの情報を集める。純正ソース、カーネル構成、復旧手段を確認する。
  2. 無送信のモジュールをビルドしてロードする。ABI・権限・復元手順を確立する。
  3. 1台の家電・1つの信号に絞る。送信波形と物理応答を確かめる。
  4. アプリからの直接操作を完成させる。エラー処理と状態の読み方を明確にする。
  5. 信号を増やす。完全フレームの照合、短押し・長押し・繰り返しの意味を確認する。
  6. 最後にネットワークと音声をつなぐ。接続維持、失敗時の無再送、UIの意味を検証する。

このハックの要点は、古い端末に機能を詰め込むことより、物理信号・送信処理・通信・表示を分けて確かめることにあった。赤外線以外のデバイスを再利用するときにも使える考え方だ。

17

参照資料と公開範囲

本文は2026年10月5日の実機調査・ソース・試験記録と使用者の動作確認をもとに編集。家庭内のアドレス、端末識別番号、認証情報、設置場所、私用の画面・ログは掲載していません。概念図は記事用に作成したもので、実測波形ではありません。

END OF FIELD NOTE 001ハック一覧へ戻る ↗