4
2
はじめての記事投稿
Qiita Engineer Festa20242024年7月17日まで開催中!

ラズパイで自作スマートリモコン LEDドライバを使ってパワーアップ!

Last updated at Posted at 2024-07-13

はじめに

  • 投稿者はソフトはそこそこに、電子工作はまったくの初心者です、お手柔らかにお願いします。
  • 読者の対象としては、こちらの記事をきっちり読んで、独力でブレッドボード上での実装&室内灯などの何かしらの家電を操作することができた人を前提とします。
  • この記事は、『書きかけの記事』という位置づけです。1年くらいかけてブラッシュアップしていくので、そこも含めてお楽しみいただければ幸いです。
  • 現状、御本尊の記事で学べることはこの記事で焼き増し記載しておりません。焼き増しにはなってしまいますが、自分なりに咀嚼した内容を記載するかどうかは検討中です。
  • ソフト側のソースコードは、GitHubにアップしています。

背景

  • スマートホームで全自動とはいかずとも、音声で電気やエアコンくらいは操作してQOLアップできたらなと思ってました
  • しかし、地政学的リスクを鑑みれば(たぶん言葉の意味違う)、宅内LANに招き入れるスマートリモコンは選ばねばなりません(※)
  • 考えに考えましたが、どう考えてもスマートリモコンをラズパイで自作するほか、reasonableな手段はありません
  • こちらの記事を参考にし、大変勉強させていただきました
  • 今回は、わが家の事情に合わせてさらに高出力化し、パワーアップさせたスマートリモコン自作伝を共有します

※自作したかっただけです。その理由のこじつけです。

わが家の事情(課題)

  • ダイキンのエアコンを動かすには、どうも飛距離が足りない(ソフトウェア側の改良はもちろん、赤外LED L12170を使って、300mAを流して高出力化を試みるも、せいぜい1mくらいが限界。わが家はスマートリモコンを置ける場所が限られているため、2mくらい離れていても確実にエアコンを動かしたい。)
  • ブレッドボード上ではうまく動くものの、基板にはんだ付けすると途端に動かなくなってしまう(原因は不明)
  • あきらめかけてラズパイにアドオンする既成の赤外線リモコンモジュールの購入も考えたが、意地でも自分で手組みしたリモコンをこの際作りたくなってしまった(なんやかんやで既製品は3000円~8000円と結構いい値段していしまう)

目標

  • ややクセのあるダイキンエアコンを2m程度離れた場所からでも安定して操作できるようにする
  • 基板へのはんだ付けを容易にするため、部品点数を極力減らし簡素化する
  • 既製品に頼らず、自作する

ちょうど同じようなことに困っていて、同じようなことを目指している方の一助になれば幸いです。

対応方針

こちらの記事では、2つのFETを活用してLEDドライバ回路を作っていましたが、今回はLEDドライバNJW4617DL3を用いて回路設計します

回路全体像

image.png

お買い物リストもご覧いただきたいですが、主要な電子部品は下記になります。

  • NJW4617 x1
  • 0.68Ω抵抗 x1
  • 高出力赤外線LED L12170 x1~3

2つのFETの代わりに、LEDドライバを用います。

また、課題にあった飛距離を上げるため、300mAまで許容できる高出力赤外線LED L12170を用います。ちょっと高いですけどね(1個300円)。今回は安く作ることは目標としていません。(現状、3回も基板のはんだ付けを失敗しており予算なんてもはやあってないようなものですw)

後ほど詳しく説明しますが、電流を調整する目的で0.68Ωの抵抗を使います。0.68Ωは、秋月電子さんでは取り扱いがないと思います。3軒くらいとなりにある千石電商さんの地下1Fへ行ってみてください。入って左手奥にありますので。(ネットで買うならこれです)

上記は、あくまで赤外線LED点灯(送信)回路のため、リモコンコード記憶(赤外線受信)も必要であれば、御本尊の記事を参考に別途設計してください。

NJW4617DL3について

NJW4617は、PWM調光機能付き定電流LEDドライバです。定電流回路、PWM制御回路、保護回路を、小型パッケージに搭載し、LEDを簡単に点灯させることができます。
外付けの電流センス抵抗により、LED電流を可変でき、最大500mAまで設定が可能です。
出力電圧は最大40Vのため、使用するLEDのVfに応じて、多灯のLED直列接続が可能です。
また、PWM信号のDutyを変えることにより、調光も可能です。バックライトや照明、その他光源などのアプリケーションに最適です。

image.png

[1] 日清紡マイクロデバイス株式会社HP NJW4617_J.pdfより文章・画像引用

  1. VDDには、ラズパイの5V電源を入力します。
  2. High/Low制御できるGIPO17等をつなぎます。常に点灯させたければHigh入力固定すればよいそうです。今回はHigh/Lowを制御することでリモコンの信号を送ります。High/Lowの制御には、こちらの記事と同様にirrp.pyを使います。
  3. ラズパイのGNDへ接続します。
  4. ここがLEDドライバの肝で、センス抵抗というものを接続します。NJW4617は20~500mAを流せるのですが、どれくらい電流をLEDに流すかはこのセンス抵抗値で決まります。センス抵抗の片足は、3.GNDに接続します。
  5. LEDのカソード(-)側を接続します。LEDのアノード(+)側は、1.VDDと繋げます。

LEDドライバを使うメリット

  • LEDを直列につないでOK!
  • LEDごとの抵抗が不要になる!
  • 電流精度を高められる!
  • センス抵抗1つで電流の調整が容易にできる!
  • 2つのFETがこれ1つになる!
  • 保護回路がついている!

実際に、LEDドライバを利用したらスマートリモコンの安定感が増しました。(個人の感想です)
また、回路もかなり簡素化するので、オススメの手段です。

LEDドライバを使うデメリット

  • NJW4617はピン幅が狭く、はんだ付けが難しい。
  • LEDを直列につなげる、ということは、電圧がネックになる。
  • ラズパイからの電源供給は5V。L12170であればVfは1.45~1.70Vほどなので、直列接続ならばせいぜい3つが限界です(1.45~1.70×3=4.35~5.10V)。

回路詳細

詳細も記した図を以下に示します。

image.png

まず、NJW4617のVDDとラズパイの+5Vを接続します。その+5Vは、LEDのアノード(+)側にも接続してください。言わずもがなかも知れませんが、LEDの足が長いほうがアノード(+)です。

ついでに、LEDのカソード(-)側は、NJW4617のLED端子に接続します。言わずもがなかも知れませんが、LEDの足が短いほうがカソード(-)です。

次に、NJW4617のPWM入力に、ラズパイのGPIO17を接続します。言わずもがなかも知れませんが、GPIO17の理由は、こちらの記事で、irrp.pyからHigh/Low制御を行うピンをGPIO17に設定しているからです(GPIO18は赤外線受信用)。つまりソフト側の設定によっては、GPIO17から他のピンへ自由に変えることができます(どのピンでもいいわけではありません)。
話は逸れますが、可視LEDを普通に調光したい場合、PWM制御が可能なGPIO18とNJW4617のPWM入力を接続し、pigpioでPWM制御すれば、何かと色々遊べそうで良いですよね。

さらに、0.68Ωの抵抗をセンス抵抗として、Rs端子に接続します。
NJW4617のデータシート.pdfによれば、LEDに流したい電流ILEDを決めれば、以下の式から選ぶべきセンス抵抗Rsが定まります。
$$ R_{\mathrm{s}} [\mathrm{Ω}] = \frac{0.2 [\mathrm{V}]}{I_{\mathrm{LED}}[\mathrm{A}]} $$
今回は、ILED=300mAを流したいため、
$$ R_{\mathrm{s}} [\mathrm{Ω}] = \frac{0.2 [\mathrm{V}]}{0.300[\mathrm{A}]} \fallingdotseq 0.66666\cdots $$
となります。近似値の抵抗を使えばよいですが、0.6666...を下回る抵抗を選定してしまうと、L12170の最大許容電流300mAを超えてしまい、色々とよろしくないようなので、大きめの抵抗値として、今回は0.68Ωを選定したということなります。適当な抵抗を直列・並列接続して0.6666...Ωの近似抵抗を作ってももちろんOKです。
言わずもがなかも知れませんが、こちらの記事で、標準指定されている赤外線LEDOSI5LA5113Aで実装する場合、許容最大電流は100mAですので、2Ωの抵抗をセンス抵抗Rsとして使用してください(※)。2Ωなら、秋月電子さんでも取り扱いあるはずです。

※2Ω抵抗を使うことについて 本当に起こるかどうかは正直詳しくないので知りませんが、2Ωのセンス抵抗を用いて100mAぴったりを狙うのは、電流のブレがあった場合も考慮すれば、あまりよろしくないかもしれません。 2Ωよりちょっとだけ大きい抵抗用いてもよいかもしれませんね。 まあ、何かの手違いで101mAが流れてしまってもが直ちにOSI5LA5113Aが壊れることはないと思います。 責任は取りませんが。。。
2024年8月4日追記 赤外線LEDOSI5LA5113A+2.2Ωの抵抗の組み合わせも試しましたが、L12170とほぼ同等の飛距離&安定感となりました。OSI5LA5113Aは単価が12円と非常に安いので、そこはメリットかなと思います。この実験的にですが、広範囲に届くよう安定して赤外線信号を送出できるようになった主要因は、やはりLEDドライバNJW4617であると考えます。

最後に、センス抵抗の片足と、NJW4617のGND端子をラズパイのGND端子に接続すれば完成です。
言わずもがなかも知れませんが、NJW4617は最大500mAまで流すことができます。L12170の最大許容電流は300mAで、500mAを下回るので、この構成で問題ない、ということになります。

ブレッドボード、基板でもやることは変わらないと思うので、気が向いたらキレイに配線する方法を考えて、追記していきますね。

結果

目標の達成度

ダイキンエアコンを2m程度離れた場所からでも安定して操作できるようにする

CLEAR!
巻き尺で雑に測ってみましたが、2mちょいといった距離から、ダイキンエアコンを確実に操作することができるようになりました。
ソフトウェア(irrp.py)側も改良し、高出力LED L12170を用いても1mが限界でしたので、LEDドライバを活用したおかげで上手くいったのではないかと考えるのが自然かと思います。
ちなみに、ソフトウェア側の改良もめちゃめちゃ効果的です。 こちらに関しての詳細は付録をご覧ください。

ただ、飛距離ですが、感覚的には2mぐらいが限界な気がしています(※)。5mくらいは行きたいところですよね。メーカのリモコンがなぜあそこまで強力なのか理解できません。。。

※飛距離を上げることへの悩み

有識者からすれば、最大許容電流を超える電流をLEDに流しても耐える的な考えを持つ方もいるようですが、いかんせん、自分は詳しくないので論理的な説明ができず、なかなか踏ん切りがつきません。

みなさんも同じ悩み持っていませんか?正直500mAくらいはL12170に流してみたい...とw

どうもパルス幅の比(duty比)が小さい、つまり、High(LED点灯)時間に対してLow(LED消灯)時間が長ければ、最大許容電流を超える電流は流して問題ないようです。それが、L12170 データシートのパルス順電流IFPにあたります。これは『まあ、そりゃそうだよね、休み休みやれば過電流?も許されるよね』という論理かと思います。

image.png
参考までに相鉄みたいなduty比の解説画像貼っておきます。

しかしながら、リモコンコードはこのduty比、基本100%なんですよ。(こちらによれば、'1'を示すときは33%、'0'を示すときは100%のようです。)
つまり点灯と消灯の時間幅は同じです。だから基本的に最大許容電流を超える電流を流すのはよろしくないかと思います。

有識者の論理では、『常時点灯させるならまだしも、リモコンとしての用途であればたいていはずっと消灯していて、点灯したとしてもせいぜい1~2秒でしょ?だから多少超えても問題ないわけよ』というものなのかもしれません。

メーカに聞いても、責任取れないのでダメなものはダメとしか答えようがないでしょうし、こういう現場の勘、判断になるような部分、難しいですよね。本当に事故になってはいけませんし。たかがLED、されどLED。

駄文、失礼しました。。。

基板へのはんだ付けを容易にするため、部品点数を極力減らし簡素化する

CLEAR!
部品の種類はNJW4617、1つのセンス抵抗、高出力赤外LEDL12170の3つになりました。複数種類の抵抗や複数の素子は必要なくなりました。簡素化はできたと判断します。

既製品に頼らず、自作する

CLEAR!
2番目の目標の結果系なのかもしれませんが、部品点数が減り、回路が簡素化したことで、はんだ付けのミスを減らすことができました。そして、ようやくユニバーサル基板上でもまともに動く回路を自力で製作することができました。日清紡マイクロデバイス株式会社さんもニッポン企業!地政学的リスクはありません。あれ、ラズパ...

もちろん興味はあるので、既製品のラスパイAdd-on向け赤外線リモコンモジュールも買って遊んでみたいところではあります。(よくよく考えるとネガティブキャンペーンみたいになってしまい申し訳なかったです、敵対の意思はございません。)

考察

  • 飛距離が伸び、安定感が増したことについては、LEDドライバがLEDに流れる電流精度を高めてくれたからではないかと推測しています。
  • LEDごとの抵抗やFETが不要になったことで、電子部品の点数はLED×2+NJW4617DL3+抵抗×1と4点と少なくすることができました。FETの代わりにLEDドライバを使ったことが部品点数を抑えることにつながったと考えます。
  • 基板へのはんだ付けも問題なく行えたのは、回路の複雑さが減ったからと考えています。既製品に頼らず自作できました。

お買い物リスト

ラズパイ・電源ケーブル・SDカード、はんだ、はんだごて、ニッパ等、もろもろ間材費とでもいいましょうか、そういったものは省きます。
安いことに越したことはないので散財はしない方針ですが、特にコストを抑えようと徹底的に努力したりはしてません。

予算に合わせて、梅・竹・松コースに分けて記載してみました。

変化点に"+"をいれたパーツは、御本尊の記事にない部品です。

変化点 品名 価格・個数 購入目的
+ NJW4617 80円x1 LED発光させるため
+ 赤外線LED L12170 300円x2 発光により信号を家電に伝えるため
(書く必要ありますこれ?)
+ 抵抗(0.68Ω) 30円x1 センス抵抗に使用するため
ユニバーサル基板 130円x1 電子部品実装のため
ピンソケット 80円x1 ラズパイと基板接続のため
赤外線リモコン受信モジュールOSRB38C9AA 100円x1 リモコンコード登録のため
ブレッドボード・ジャンパーワイヤ(オス-メス) 15cm(黒) 280円x1 テストのため
ブレッドボード BB-801 220円x1 テストのため
+ スズメッキ線(0.5mm 10m) 330円x1 回路接続のため(あまりの抵抗があればそれの足でも可)

計1,850円と言ったところでしょうか。Raspberry Pi Zero WH が現在4,280円なので、電源ケーブルやはんだごて、はんだなどもろもろ入れても1万円には収まるでしょう。

梅に加えて、下記を追加購入することをオススメします。梅にはないものには、変化点に"+"をつけています。

変化点 品名 価格・個数 購入目的
+ はんだ吸取線 CP-3015 210円x1 私のような人間には空気のようになくてはならない存在です。『私、失敗しちゃうんで。』
+ Raspberry Pi Dedicated Breadboard 1650円x1 ラズパイのピンソケットに直接接続(スタック)できるブレッドボードです。基板へのはんだ付けに自信がない方はもうこれに実装して終わりでよいのではないでしょうか?
+ 5mm白色0.5WLED OSW4XA5141R-150MA 250円x1 動作確認目的。スマホカメラで赤外線は撮れる、といいつつも見にくいです。素直に可視LEDで見るほうが楽かと思います。これのために買い足す抵抗は、御本尊の記事で紹介されていた赤外線LED(OSI5LA5113A)のためにも使えるので、順電流maxが高いものをわざわざ選びました。テスト目的だったので放熱対策はしませんでしたが、本来必要なようです。
+ カーボン抵抗(炭素皮膜抵抗) 1/4W2.2Ω 100円x1 動作確認用LED(5mm白色0.5WLED OSW4XA5141R-150MA)のセンス抵抗として購入します。持っておけば赤外線LED(OSI5LA5113A)のセンス抵抗としてもそのまま使えます。
NJW4617 80円x1 予備
赤外線LED L12170 300円x2 予備
抵抗(0.68Ω) 30円x1 予備
ユニバーサル基板 130円x1 予備
ピンソケット 80円x1 予備
赤外線リモコン受信モジュールOSRB38C9AA 100円x1 予備

竹に加えて、下記を追加購入することはオススメしません。ただ、こういった記事を読む方は結局買う気がしています。長い目で見て色々と遊ぶのであれば、買っちゃえばいいんじゃないでしょうか?

梅・竹にはないものには、変化点に"+"をつけています。

変化点 品名 価格・個数 購入目的
+ ディジタルマルチメータ AD-5503 3580円x1 買うか悩ましいのですが、回路のデバッグにはあったほうが良いかなと思い買ってしまいました。千石電商さんのたしか2Fか3Fにありました。ホントに3580円です。現地に行く用事があればネットより安くて良いのではないでしょうか?
+ 温湿度センサー モジュール DHT20 420円x1 これ見よがしに買ってみました。使い道未定。ホントは赤外線人感センサも欲しい。あとカメラモジュールも。

大人の趣味として、420円でセンサ買って、室内温度に合わせてエアコン制御なんて、夢が広がりますよね。持つべきものを持ってしまえば、安くて永遠に楽しめるのが電子工作の魅力ですね。最初こそ1万円かかりますが、その後の拡張性と拡張時の低コストさを鑑みれば、電子工作、大人の趣味にはもってこいな気がします。

いろいろと遊んでみようと思っています。

残課題リスト

  • irrp.pyの改良コードの解説を記載します (実施済)
  • ダイキンのややクセのあるリモコンコードに対して調べたこと、ソフト側の対処法をまとめます (実施済)
  • 独自に改良したirrp.pyのソースコード全体をgitで公開します (実施済、GitHubへのリンク)

ここまでの上記2つは24年8月連休終わりまでにはまとめます!と宣言しておかないとさぼりそうなので... 達成しました!

  • わが家のスマートリモコン指示系統の戦略をまとめて追記します(御本尊の記事ではSlackを用いていましたが、うちでは別の手段を使っています)
  • 受信したリモコンの波形を簡易的に(matplotlib等でラフに)可視化するツールを作り公開します
  • ソフトの機能を拡充します(たとえばエアコンの切タイマを設定できる等。よくよく考えたら切入タイマってソフト側でもカバーできますよね。例えば『朝6時の時点で天気予報の気温が28℃だったらエアコンをつける』とかもできますよね)
  • 効率的な回路設計を再考します
  • NJW4617の狭い端子にうまくはんだ付けする個人的な裏技を記載します
  • まだ試せていませんがNJW4617のような素子向けソケットなるものがあるようですので、また調べて更新します
  • LEDピン電圧の計算をしてませんでした、ざっくりと計算し多分大丈夫そうなのですが、しっかり記載したいです(L12170であれば2つが限界、3つはNGかもしれません)
  • Raspberry Pi Zeroの価格高騰のため、Raspberry Pi Pico版の作成にも挑戦予定です。これで制作費用は総額2000-3000円くらいには収められるはずです...
  • 誤字脱字があれば修正します

ソースコード

GitHubにアップロードしています。

さいごに

ご覧いただきありがとうございました。なにかあればコメントください。

付録

ダイキン製エアコンに対応する

結果のセクションで下記申し上げました。

ソフトウェア側の改良もめちゃめちゃ効果的です。

こちらの付録では、ダイキン製エアコンに対応するために、ソフトウェア側に施した処置について、ダイキン製エアコンに対する解析つきで説明します。

まずはダイキン製エアコンのリモコンコードの送信フォーマットの解説をします。いきなり『こういう対策をすれば効果があります』と書いても読者を置いてけぼりにすると思いますので。

ダイキンのリモコン送信フォーマットについて

こちらこちらのサイトが非常に勉強になるのですが、ダイキン製エアコンのリモコン送信フォーマット(※)は抽象的に書くと下図のようになります。

※エアコンのリモコン送信フォーマット規格について

エアコンのリモコン送信フォーマットは規格化されているようでして、主に3種類あります。

  1. 家電製品協会(家製協)のフォーマット
  2. NECのフォーマット
  3. SONYのフォーマット

ダイキン製は、1. 家製協のフォーマットではあるのですが、独自のカスタマイズが入っているため、"クセのある"という愛情を込めさせていただいた次第です。
実際に、私も三菱の霧ケ峰では何ら問題なく動いていたスマートリモコンが(三菱も家製協のフォーマット)、ダイキンのエアコンを前にするとビクともしなくなる、というのを経験しました。

image.png

図のように、3つのセクションからなっており、青とオレンジのセクションの間はおよそ25msほど、オレンジと緑のセクションの間はおよそ35msほどLowを保持する休止時間のようなものがあります。

ダイキン製エアコンを操作するためのポイントは、この休止時間ごとに、pigpioのメソッドpi.wave_delete()を伴って、計3回、信号を分割して送信する必要があります(pi.wave_delete()の詳細については、irrp.pyのコード解説をご覧ください)。

こちらのサイトでは、irrp.pyがリモコンのコードを記憶しているcodesファイルを手動で編集して、1つにまとまっているリモコンコードを3つに分割することで、pi.wave_delete()を伴った分割送信を実現しています。対策法の詳しい解説の前に、まずはirrp.pyのファイルcodesのざっくりとした仕様を前提として解説します。

irrp.pyのファイルcodesのざっくりとした仕様

ファイルcodesをvim等のエディタで開くと、下図のように、辞書型で記載された数値の羅列が見えるかと思います。

image.png

辞書のキーには、irrp.pyで指定するコマンド名が、バリューには、リモコンコードが配列で記載されています。
こちらの配列では、LED点灯(High)時間と消灯(Low)時間が交互に並んでいます。単位はマイクロ秒です。

よーく目を凝らして数値の羅列を見ていると、先ほど紹介した25msの休止期間にあたる記載も見つけられるはずです。25000μsなどと、キレイな数値があるわけではなく、実測値のブレもあり、おそらく249600あたりの数値がcodesで見えるかと思います。

休止期間の数値はかなり大きい値のため、ボーっと見ていても気づけるものだと思います。

『分割する場所はどこか?』というアタリがつけられたところで、話を戻して分割送信の手段を考えます。

分割送信のやり方①

先ほど、

25ms・35msの休止時間ごとに、pigpioのメソッドpi.wave_delete()を伴って、信号を分割して送信する必要があります。

と申し上げました。上述したcodesの仕様を踏まえると、信号を分割する場合は、下記のような方法でcodesを編集すればよい、ということになります。

{“aircon-on-1st-frame”: [425, 425, 425, 425, 425, 425, ..., 425, 25000]},
{“aircon-on-2nd-frame”: [425, 425, 425, 425, 425, 425, ..., 425, 35000]},
{“aircon-on-3rd-frame”: [425, 425, 425, 425, 425, 425, ..., 425, 425]}

irrp.pyには、3つの信号を順に送信するよう指示します。詳細は、元サイトを参考にされてください。

私が使用したコマンドは下記になります。わが家の場合、--gapは5がもっとも反応が良かったです。

python irrp.py -p -g17 -f codes --gap 5 aircon-on-1st-frame aircon-on-2nd-frame aircon-on-3rd-frame

分割送信のやり方②

筆者は、『長すぎる休止期間(15ms以上)があった場合は、そこで信号の送信を一旦切る』というコンセプトのもと、分割送信を実現しました。

まず、15ms以上で、配列を分割する関数をirrp.pyに追加します。サボってchatGPTに書いてもらいました。

def split_array_by_thres(arr, thres):
    result = []
    subarr = []

    for item in arr:
        subarr.append(item)
        if item >= thres:
            result.append(subarr)
            subarr = []

    if subarr:
        result.append(subarr)

    return result

これによって、配列は下記のように分割されます。(一例)

Before: [425, 425, 425, 425, 425, 425, 425, 25000, 425, 425, 425, 35000, 425, 425]
After: [[425, 425, 425, 425, 425, 425, 425, 25000], [425, 425, 425, 35000], [425, 425]]

あとは、分割された配列をfor文で順に送信すればよいということになります。

ということで、irrp.pyのLED点灯制御プログラムを下記のように書き換えます。

for arg in args.id:
      if arg in records:

         codes= records[arg]
         codes_split = split_array_by_thres(codes, 15000) # ※

         for code in codes_split:
            # Create wave
            marks_wid = {}
            spaces_wid = {}

            wave = [0]*len(code)

            (中略)


            for i in spaces_wid:
               pi.wave_delete(spaces_wid[i])

            spaces_wid = {}
      else:
         print("Id {} not found".format(arg))

ポイントは、codesを先ほど用意した関数split_array_by_thresを使って多次元リストにreshapeする点です。(コード内の※参照)
その後、分割したフレームをfor文を使って順に送出します。
これによって、コンセプトである『長すぎる休止期間があった場合は、そこで信号の送信を一旦切る(pi.wave_delete()を伴って)』を実現します。

書いていて気づいたのですが、上記コンセプトを愚直に設計に落とすのであれば、splitする際に、『今の要素はHighを意味するものか、Lowを意味するものか』を区別して、『Low(休止)の場合のみ分割する』設計とした方がよさそうですね。実装としては、奇数偶数で区別すればOKですかね。

こちら時間があれば直します。現状は、『長すぎる休止期間(=Lowの場合)』に加え、『長すぎる点灯時間(=Highの場合)』があった場合でも、信号の送信が一旦区切られてしまう実装となっていますね。細かいですが100%コンセプト通りの実装とは言えないと思います。

こちらの手段を選ぶ場合のPythonコマンドは下記となります。冷房を入れるコマンドをaircon:coolで記憶させた場合です。プログラムが勝手にリモコンコードを分割するので、codesのキー(コマンド)の指定は、1つとなります。

python irrp.py -p -g17 -f codes aircon:cool

[調査中]中略部分のコード理解

中略した部分のコードは何をしているのか、自分なりの解釈を記載しておきます。雰囲気で読んだだけなのであまり精度は信頼しないでください。
詳細はこちらをご参照ください。

wave = [0]*len(code)

for i in range(0, len(code)):
    ci = code[i]
    if i & 1: # Space
       if ci not in spaces_wid:
          pi.wave_add_generic([pigpio.pulse(0, 0, ci)])
          spaces_wid[ci] = pi.wave_create()
       wave[i] = spaces_wid[ci]
    else: # Mark
       if ci not in marks_wid:
          wf = carrier(GPIO, FREQ, ci)
          pi.wave_add_generic(wf)
          marks_wid[ci] = pi.wave_create()
       wave[i] = marks_wid[ci]

codesファイルに記載の信号High時間、Low時間の羅列を整理している部分です。
Highは、marks_widに、Lowはspaces_widに分類し、pi.wave_add_generic()pi.wave_create()を使って、GPIOが出力しやすい形式の送信コマンド(波形)を作っているようです。
HighとLowは交互に来るので、i & 1を使って偶数・奇数を判定しています。
最終的な波形はwaveというリストに格納します。

delay = emit_time - time.time()

if delay > 0.0:
    time.sleep(delay)

pi.wave_chain(wave)

if VERBOSE:
    print("key " + arg)

while pi.wave_tx_busy():
    time.sleep(0.002)

 emit_time = time.time() + GAP_S

実際に送信を行う箇所はここになります。pi.wave_chain(wave)で波形waveを送信することになります。なお、このpi.wave_chain(wave)で一度に送出できるデータ量は決まっており、かなり少ないです。長めのリモコンコードを送ろうとすると、pigpio.error: 'chain is too long'というあるあるのエラーが出て動きませんので、後述の対策を導入する必要があります。

pi.wave_tx_busy()は、波形送信中でbusy状態のときに立つフラグのようです。ということは、pi.wave_chain(wave)自体は非同期で動くと推測できます。
送信が終わるまで、待機ループにて待ちます。

for i in marks_wid:
    pi.wave_delete(marks_wid[i])

marks_wid = {}

for i in spaces_wid:
    pi.wave_delete(spaces_wid[i])

spaces_wid = {}

marks_widspaces_widで生成した波形を削除し、リセットしているようです。
ダイキン製エアコンに対応するために、3分割でリモコンコードを送出する場合は、このpi.wave_delete()を各回の最後に実施する必要がありました。

chain is too longへの対策

先ほど言及した、下記に対する対策を導入します。

pi.wave_chain(wave)で一度に送出できるデータ量は決まっており、かなり少ないです。長めのリモコンコードを送ろうとすると、pigpio.error: 'chain is too long'というあるあるのエラーが出て動きませんので、後述の対策を導入する必要があります。

思想自体は、15ms以上で配列を分割するものとほぼ変わりません。下記のような関数を用意します。

def split_array(arr, N):
    return [arr[i:i + N] for i in range(0, len(arr), N)]

Nを与えると、配列を1つあたりN個の要素で分割してくれる関数になります(最後はあまりがでることがあります)。
例を出すまでもないですが、N=4のときは下記のようになります。

Before: [425, 425, 425, 425, 425, 425, 425, 25000, 425, 425, 425]
After: [[425, 425, 425, 425], [425, 425, 425, 25000], [425, 425, 425]]

あとは、pi.wave_chain(wave)を使って、分割した信号を順に送出してもらいます。コードの全体像はおおむね下記の通りになります。

MAX_WAVE_LEN = 512

for arg in args.id:
      if arg in records:

         codes= records[arg]
         codes_split = split_array_by_thres(codes, 15000)

         for code in codes_split:
            # Create wave
            
            marks_wid = {}
            spaces_wid = {}

            (中略)

            delay = emit_time - time.time()

            wave_split = split_array(wave, MAX_WAVE_LEN) # ※
            for wave_bank in wave_split:
               if delay > 0.0:
                  time.sleep(delay/len(wave_split))
               pi.wave_chain(wave_bank)
               while pi.wave_tx_busy():
                  time.sleep(0.002)

            if VERBOSE:
               print("key " + arg)

            emit_time = time.time() + GAP_S

            (中略)
        
      else:
         print("Id {} not found".format(arg))

ポイントは、waveを先ほど用意した関数split_arrayを使って多次元リストにreshapeする点です。(コード内の※参照)
Nは、MAX_WAVE_LENに相当します。適当な場所で512等の数値を宣言しておきます(コード冒頭参照)。MAX_WAVE_LENの値は'chain is too long'が出ない範囲で設定します。

ちなみに、ダイキン製エアコンに対応する場合は、先述の3分割送信も実装する必要がありますが、三菱はこの対策のみでちゃんと動くようになりました。

ダイキンは、こちらの対策だけしてもエアコンは動きませんでした。むろん、'chain is too long'のエラーは出なくなりますが... ダイキンのエアコンを動かすのであれば3分割法が効果的です。

ソースコード(対象部分のみ抜粋)

ソースコード全体もコピペしておきます。全体といいつつ、あまりに長いので部分的に載せます。
githubにも上げたいと思います。

クリックして全体表示(中略に注意)
MAX_WAVE_LEN = 512

def split_array(arr, N):
    return [arr[i:i + N] for i in range(0, len(arr), N)]

def split_array_by_thres(arr, thres):
    result = []
    subarr = []

    for item in arr:
        subarr.append(item)
        if item >= thres:
            result.append(subarr)
            subarr = []

    if subarr:
        result.append(subarr)

    return result

pi = pigpio.pi() # Connect to Pi.


if not pi.connected:
   exit(0)

if args.record: # Record.

    (中略)

else: # Playback.

   try:
      f = open(FILE, "r")
   except:
      print("Can't open: {}".format(FILE))
      exit(0)

   records = json.load(f)

   f.close()

   pi.set_mode(GPIO, pigpio.OUTPUT) # IR TX connected to this GPIO.

   pi.wave_add_new()

   emit_time = time.time()

   if VERBOSE:
      print("Playing")

   for arg in args.id:
      if arg in records:

         codes= records[arg]
         codes_split = split_array_by_thres(codes, 15000)

         for code in codes_split:
            # Create wave

            marks_wid = {}
            spaces_wid = {}

            wave = [0]*len(code)

            for i in range(0, len(code)):
               ci = code[i]
               if i & 1: # Space
                  if ci not in spaces_wid:
                     pi.wave_add_generic([pigpio.pulse(0, 0, ci)])
                     spaces_wid[ci] = pi.wave_create()
                  wave[i] = spaces_wid[ci]
               else: # Mark
                  if ci not in marks_wid:
                     wf = carrier(GPIO, FREQ, ci)
                     pi.wave_add_generic(wf)
                     marks_wid[ci] = pi.wave_create()
                  wave[i] = marks_wid[ci]

            delay = emit_time - time.time()

            wave_split = split_array(wave, MAX_WAVE_LEN)
            for wave_bank in wave_split:
               if delay > 0.0:
                  time.sleep(delay/len(wave_split))
               pi.wave_chain(wave_bank)
               while pi.wave_tx_busy():
                  time.sleep(0.002)

            if VERBOSE:
               print("key " + arg)

            emit_time = time.time() + GAP_S

            for i in marks_wid:
               pi.wave_delete(marks_wid[i])

            marks_wid = {}

            for i in spaces_wid:
               pi.wave_delete(spaces_wid[i])

            spaces_wid = {}
      else:
         print("Id {} not found".format(arg))

pi.stop() # Disconnect from Pi.

以上、ダイキン製エアコンが動かせん!とお困りの方の一助になれば幸いです。

リモコンコード登録(赤外線受信)時のTIPS

ダイキン製エアコンのコードはかなり長いため、受信時間パラメータの--postを長めにとっておいた方が良いようです。

python irrp.py -r -g18 --post 512 --short 100 -f codes aircon:cool

こちらのサイトの調査に基づけば、最低でも400msくらい準備していればOKそうです。

4
2
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2