Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
1
Help us understand the problem. What is going on with this article?
@on0z

homebridgeでエアコンをスマート化

暑いですね.エアコンのリモコンコードを解析しましょう.

環境

HomeKitを使います.
解析したリモコンコードを,homebridge-cmd4と学習リモコン基板をつかって送出します.

ラズパイ

pi@remocon:~ $ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:    10
Codename:   buster
pi@remocon:~ $ uname -a
Linux remocon 5.4.51-v7+ #1327 SMP Thu Jul 23 10:58:46 BST 2020 armv7l GNU/Linux

名前を変えていますが,raspberry pi 3Bです.

pi@remocon:~ $ node -v
v12.18.3
pi@remocon:~ $ npm -v
6.14.6
pi@remocon:~ $ homebridge --version
1.1.1

homebridge-cmd4は2.3.2を使用しています.

赤外線送出

回路を組むのが面倒なので,キットを使っています.そのうち自分でも組もうと思っています.
リモコンいじり放題!!ラズパイ専用 学習リモコン基板で使えるソフトウェア3種詰め合わせ公開

エアコン

三菱エアコン 霧ヶ峰 MSZ-GV2219-W です.
リモコンはRH191

解析方法

ひたすらリモコンコードを記録し,それぞれを照らし合わせてどこが何の情報なのかを探すだけです.

作業しやすいように,Macからsshでターミナル操作,且つsshfsでファイルアクセスしています.

参考サイト

先に見た方が理解が楽かもなので上に置いておく

赤外線リモコンデータの変換
エアコンのリモコン信号を解析する ~デコード編~
Raspberry Piで三菱のエアコンのリモコンをスキャン・解析・送信してみる
赤外線リモコンの通信フォーマット
ラズベリー・パイ専用 学習リモコン基板 ADRSIRの使い方
LEDシーリングライト用赤外線リモコンの製作(1)アイリスオーヤマ用(10/21更新)

下準備

私も前はirrp.pyをよく使っていましたが,今はキットを使っています.
そのため,エクスポートされるデータの形式が異なります.

irrp.pyの場合
オンの時間,オフの時間,オンの時間,,,となっている.

[8950, 4440, 585, 529, 585, 529, 585, 529, 585, 529, 585, 529, 585, 529, 585, 1636, 585, 529, 585, 1636, 585, 1636, 585, 1636, 585, 1636, 585, 1636, 585, 1636, 585, 529, 585, 1636, 585, 529, 585, 1636, 585, 529, 585, 529, 585, 1636, 585, 529, 585, 529, 585, 529, 585, 1636, 585, 529, 585, 1636, 585, 1636, 585, 529, 705, 1636, 585, 1636, 585, 1636, 585, 39828, 8950, 2210, 585, 95511, 8950, 2210, 585],

ADRSIR 学習リモコン基板の場合
これも実はオンの時間,オフの時間,オンの時間,,,となっている.

830043001100320011003200110011001200110011001100120032001100110011001100120032001100320011001100120032001100110011001100120032001100320011001100110033001100310012001000120011001200320011001100110011001200320011001100110011001200110012001000120011001200100012001100120010001200110012001100110011001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001100120010001200310012003200110011001200100012001100120010001200320011001100120032001100100012001100120011001200100012001100120032001100320011001000120032001100330011001000120011001200100012001100120010001200110012001000120011001200310012001000120011001200100011001100120011001100110012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120011001100110012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001100120010001200110012001000120011001200100012003200110011001200320011003100120011001200320011001000120031001200ED018300430012003100120032001100100012001100120010001200320011001100120010001200330011003100120010001200310012001100120010001200320011003300110010001200310012003100120011001200100012003200110011001200100012003200120010001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001100120010001200110012003100120031001200100012001100120010001200110012003100120011001200310012001000120011001200100012001100120010001200320011003200120010001200310012003100120011001200100012001100120010001200110012001000120011001200110011003300110010001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200110011001100120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200110012001000120011001200100012001100120010001200110012001000120011001200100012001100120010001200110012001000120011001200110012001000120031001200110012003100120031001200100012003300110010001200310013004205

irrp.pyは、ONとOFFの期間の時間(μs)
「"tv:on": [8950, 4440, 585, 529,...」の場合、最初の「8950」は「ONが8950μS(約9ms)」、次の「4440」は「OFFが4440μS(約4.4ms)」という意味。これは、東芝のテレビが用いている赤外線リモコンのフォーマットであるNEC方式のリーダーコード、ONが9ms、OFFが4.5msにほぼ合致する。
CSVファイルは、ONとOFFの期間のパルスの数
「5601 A900 1800 1500 ...」の場合、最初の「5601」は「0156h」、十進数で「342」で「ONが342」、次の「A900」は「00A9h」、十進数で「169」で「OFFが169」という意味。
 多くのリモコンの赤外線信号は38kHzで変調されているため、1つのパルスの時間は1/38000=0.000026315秒、約26μsになる。これに上記のCSVファイルの例の最初の値「342」をかけると、342x26=8892、となり、これは、irrp.pyの例の最初の値「8950」とほぼ同じになる。同様に、次の値「169」に26をかけると、169x26=4394、となり、これもirrp.pyの例の二番目の値「4440」とほぼ同じ。
 どうやら、irrp.py=時間、CSVファイル=パルス数、で間違いないようだ。

だそうです.CSVファイルというのは,学習リモコン基板用に公開されている,リモコンコード集CSVのことです.
ちなみに,そのCSVファイルの中に,我が家のエアコンっぽいコードはありませんでした.

解析をするにはこのままだとわかりにくいので,ADRSIR生データから16進数に変換するコードをチャチャっと書きます.

import Foundation

extension String{
    /// 指定の長さで区切る
    func split(by length: Int) -> [String]{
        var tmpString = ""
        var splitted: [String] = []
        for (i, c) in self.enumerated(){
            tmpString.append(c)
            if (i + 1)%length == 0{
                splitted.append(tmpString)
                tmpString = ""
            }
        }
        if tmpString != ""{
            splitted.append(tmpString)
        }
        return splitted
    }
}

enum RemoconFormat{case AEHA, NEC, Sony, Unknown}

func convertAdrsirToHex(ADRSIR: String, format: RemoconFormat = .AEHA) -> String{
    /// 4文字ごとに区切る
    let pulseCounts: [String] = ADRSIR.split(by: 4)
    guard pulseCounts.count % 2 == 0 else {
        print("pulseCounts.countがおかしい")
        exit(2)
    }

    /// 元データは38kHzパルスの数を2バイトずつのリトルエンディアンで表しているので,入れ替える必要がある.
    /// "8400" → 0084H → 132d
    /// 賢い人なら,前から2文字ずつ区切って先頭から直に10進数化するかもね.
    var formattedPulseCounts: [Int] = []
    for pulseCount in pulseCounts{
        /// 簡単に説明すると,pulseCountは絶対に4文字のはずです.
        /// それを2文字と2文字に分割し,逆にして結合した文字列を作ります.
        /// それをInt(_: radix:)に放り込んで10進数にし,配列に放り込んでいます.
        formattedPulseCounts.append(
            Int(
                pulseCount.split(by: 2).reversed().joined(),
                radix: 16
            ) ?? 0
        )
    }

    if format == .Unknown{
        print(formattedPulseCounts)
    }

    if format == .AEHA || format == .NEC{
        /// パルス数をビットに変換していく
        /// ONが1T,OFFが1T続けば0.ONが1T,OFFが3T続けば1となる
        /// NECフォーマットでは、ONが16T、OFFが8T続いた後、上記のデータ部が始まります。一方、家製協(AEHA)フォーマットでは、ONが8T、OFFが4T続いた後、上記のデータ部が始まります。
        var bits: String = ""
        for i in stride(from: 0, to: formattedPulseCounts.count, by: 2){
//            print(formattedPulseCounts[i], formattedPulseCounts[i + 1])
            if formattedPulseCounts[i] > 100 {
                /// leader
                continue
            }
            if formattedPulseCounts[i+1] > 100 {
                /// leader
                continue
            }
            if (formattedPulseCounts[i + 1] / formattedPulseCounts[i]) >= 2{
                bits.append("1")
            }else{
                bits.append("0")
            }
        }

        var bytes: String = ""
        for byte in bits.split(by: 8){
//            print(byte)
            bytes.append(String(format: "%02X", Int(byte, radix: 2) ?? 0))
        }

        return bytes
    }
    return ""
}

この作業はMacでやればいいので,swiftで書きました.Foundationだけで書いたし,raspiでも動くかな?
作った関数に生データを入れて,16進数に変換して解析します.

解析

0ベースでバイトカウントをします!

よく使うやつを見比べてみる

まず手始めに,よく使うやつを見比べてみましょう.もし簡単やったら手間が省ける.

モード 温度 風速 風向
                   0 1 2 3 4  5  6  7  8  9 10111213141516 17 18
冷房 26 Auto Auto   C4D3648000 04 18 50 6C 02 00000000000000 B3 C4D3648000 04 18 50 6C 02 00000000000000 B3
冷房 27 Auto Auto   C4D3648000 04 18 D0 6C 01 00000000000000 70 C4D3648000 04 18 D0 6C 01 00000000000000 70
除湿  Auto Auto     C4D3648000 04 08 10 4C 02 00000000000000 FD C4D3648000 04 08 10 4C 02 00000000000000 FD
暖房 20 Auto Auto   C4D3648000 04 10 20 0C 02 00000000000000 8D C4D3648000 04 10 20 0C 02 00000000000000 8D
Off (冷26AutoAuto)  C4D3648000 00 18 50 6C 02 00000000000000 B5 C4D3648000 00 18 50 6C 02 00000000000000 B5

意味がありそうなところで区切ってみました.同じデータの繰り返しになっています.
0〜4バイト目は同じですね.あと,10〜16バイト目も同じです.17バイト目はチェックバイトでしょうか.

0〜4,10〜16バイト目は固定である

2回繰り返されている

どのボタンが押されたかではなく,設定そのものが送信されるのが一般的なエアコンらしいです.冷26AutoAutoの状態でOffを押した時のデータは,冷房 26 Auto Autoに設定した時と6〜9バイト目が同じなので,これもそうでしょうか.
5バイト目はOnなら04,Offなら00でしょうね.

5バイト目は電源

6〜9バイトを見ていきましょう.

風速 風向

風速を変えてみる

                   0 1 2 3 4 5  6 7 8  9 10111213141516 17 18
冷房 26 Auto Auto   C4D364800004 18506C 02 00000000000000 B3 C4D36480000418506C0200000000000000B3
冷房 26 1 Auto      C4D364800004 18506C 82 00000000000000 73 C4D36480000418506C820000000000000073
冷房 26 2 Auto      C4D364800004 18506C 42 00000000000000 F3 C4D36480000418506C4200000000000000F3
冷房 26 3 Auto      C4D364800004 18506C C2 00000000000000 0B C4D36480000418506CC2000000000000000B
冷房 26 Auto Auto   C4D364800004 18506C 01 00000000000000 B0 C4D36480000418506C0100000000000000B0
冷房 26 1 Auto      C4D364800004 18506C 82 00000000000000 73 C4D36480000418506C820000000000000073

9バイト目の上半分が変わりました

風向を変えてみる

                     0 1 2 3 4 5  6 7 8  9 10111213141516 17 18
冷房 26 Auto 上
                    C4D364800004 18506C 12 00000000000000 AB C4D36480000418506C 12 00000000000000 AB
冷房 26 Auto 中上
                    C4D364800004 18506C 0A 00000000000000 BB C4D36480000418506C 0A 00000000000000 BB
冷房 26 Auto 中
                    C4D364800004 18506C 1A 00000000000000 A7 C4D36480000418506C 1A 00000000000000 A7
冷房 26 Auto 中下
                    C4D364800004 18506C 06 00000000000000 B7 C4D36480000418506C 06 00000000000000 B7
冷房 26 Auto 下
                    C4D364800004 18506C 16 00000000000000 AF C4D36480000418506C 16 00000000000000 AF
冷房 26 Auto スイング
                    C4D364800004 18506C 1E 00000000000000 A0 C4D36480000418506C 1E 00000000000000 A0
冷房 26 Auto Auto
                    C4D364800004 18506C 01 00000000000000 B0 C4D36480000418506C 01 00000000000000 B0

9バイト目の下半分が変わりました

冷房 26度なら6〜8バイトは固定でした.変わったのは9バイト目のみ.
さらに気になるのが,風速を変えた時に下4ビットは0x2 or 0x1でほぼ固定.

9バイト目をバイナリにしてみます

冷房 26 Auto Auto
                    0000 0010
冷房 26 1 Auto
                    1000 0010
冷房 26 2 Auto
                    0100 0010
冷房 26 3 Auto
                    1100 0010
冷房 26 Auto Auto
                    0000 0001
冷房 26 1 Auto
                    1000 0010
冷房 26 Auto 上
                    0001 0010
冷房 26 Auto 中上
                    0000 1010
冷房 26 Auto 中
                    0001 1010
冷房 26 Auto 中下
                    0000 0110
冷房 26 Auto 下
                    0001 0110
冷房 26 Auto スイング
                    0001 1110
冷房 26 Auto Auto
                    0000 0001

上2ビットは風速ですね.1ビット0固定を挟んで3ビットが風向.そして下2ビットはなんだろうか.

実機があったから気づいたんですが,巡回タイプの風速風向変更ボタン,Autoに戻ってきたときだけ「ピピッ」って言うんです.通常は「ピッ」.
そのときだけ下2ビットが10ではなく01になっている.
さらにさらに,モード切り替えで冷房に戻ってきた時も「ピピッ」と言います.

そう.これです.

↓「よく使うやつを見比べてみる」で出てきたコード.内容がほぼ同じなのに,9バイト目が異なる

冷房 26 Auto Auto   C4D3648000 04 18 50 6C 02 00000000000000 B3 C4D3648000 04 18 50 6C 02 00000000000000 B3
冷房 27 Auto Auto   C4D3648000 04 18 D0 6C 01 00000000000000 70 C4D3648000 04 18 D0 6C 01 00000000000000 70

この冷房 27 Auto Autoは,モード切り替えで巡回して戻ってきた時に記録したんでしょう(覚えてない)

9バイト目

3ビット 3ビット 2ビット
風速 固定 風向
1 0b100 上 0b100 ピッ 0b10
2 0b010 中上 0b010 ピピッ 0b01
3 0b110 中 0b110
自動 0b000 中下 0b001
下 0b101
スイング 0b111
自動 0b000

温度

さあどんどんいきましょう.
このエアコンは,冷房,暖房共に温度範囲が16〜31度でした.
バーっと記録してみました.
一部記録に失敗し,再記録が面倒になって抜けているところがあります.
6〜8バイト目を抜き出しました.

冷房 30 Auto Auto (rec 温度上げ操作
                    18 70 6C
                    0001 1000 0111 0000 0110 1100
冷房 29 Auto Auto (rec 温度下げ操作
                    18 B0 6C
                    0001 1000 1011 0000 0110 1100
冷房 28 Auto Auto (rec 温度上げ操作
                    18 30 6C
                    0001 1000 0011 0000 0110 1100
冷房 27 Auto Auto (rec 温度下げ操作
                    18 D0 6C
                    0001 1000 1101 0000 0110 1100
冷房 26 Auto Auto (rec 温度下げ操作
                    18 50 6C
                    0001 1000 0101 0000 0110 1100
冷房 25 Auto Auto (rec 温度下げ操作
                    18 90 6C
                    0001 1000 1001 0000 0110 1100
冷房 24 Auto Auto (rec 温度下げ操作
                    18 10 6C
                    0001 1000 0001 0000 0110 1100
冷房 23 Auto Auto (rec 温度下げ操作
                    18 E0 6C
                    0001 1000 1110 0000 0110 1100
冷房 22 Auto Auto (rec 温度上げ操作
                    18 60 6C
                    0001 1000 0110 0000 0110 1100
冷房 20 Auto Auto (rec 温度上げ操作
                    18 20 6C
                    0001 1000 0010 0000 0110 1100
冷房 19 Auto Auto (rec 温度上げ操作
                    18 C0 6C
                    0001 1000 1100 0000 0110 1100
冷房 17 Auto Auto (rec 温度上げ操作
                    18 80 6C
                    0001 1000 1000 0000 0110 1100
冷房 16 Auto Auto (rec 温度下げ操作
                    18 00 6C
                    0001 1000 0000 0000 0110 1100

18 ** 6Cはこの場合では固定のようです.モードに関係すると予想できます.
7バイト目を詳しくみていきましょう.
よくみると上位4ビットしか変わっていません.下4ビットはずっと0です.

30 0111
29 1011
28 0011
27 1101
26 0101
25 1001
24 0001
23 1110 
22 0110
21
20 0010
19 1100
18 
17 1000
16 0000

う〜ん.これ逆にしてみましょうか.

30 0111 → 1110
29 1011 → 1101
28 0011 → 1100
27 1101 → 1011
26 0101 → 1010
25 1001 → 1001
24 0001 → 1000
23 1110 → 0111
22 0110 → 0110
21 → 
20 0010 → 0100
19 1100 → 0011
18  → 
17 1000 → 0001
16 0000 → 0000

なるほど.n度の時なら, (n-16) の二進数の逆読みっぽいですね.間の18, 21度の信号も予想がつきますね.

7バイト目 上位4ビットは温度で,n度の時なら(n-16)の二進数の逆読み.下4ビットは固定で0

見返せば,風速,風向も二進数の逆読みっぽいですね.

モード

最初のこれを使います.6〜8バイト目を抜き出します.

冷房 26 Auto Auto   18 50 6C
除湿  Auto Auto     08 10 4C
暖房 20 Auto Auto   10 20 0C

冷房の時,26度なので7バイト目は0x50,暖房の時も20度で0x20.あってますね.除湿の時の0x10が気になりますが,24度...そんなのにしてたかなあ.
冷房の時,温度や風速風向を変えても18 ** 6Cが変わらなかったので,6バイト目と8バイト目がモードなのかなと思われます.

モードの解析をすると言うことは,一時的にでも暖房にしなければいけないと言うことです.暖房時の詳しい解析は勘弁してください.やる気があれば冬にやります.

タイマー,除湿調整,内部クリーン

タイマーはHomeKitのオートメーションを使います.
除湿調整は...ちょっと利用頻度が低いので,解析欲が湧きません.
内部クリーンは...私が知っているエアコンは,必要な時にエアコンが勝手に始めるので私が直接手を下すことはない?

と言うわけでこいつらはパスです.多分0の海にいろいろあるんだと思います.除湿調整はもしかしたら温度フィールドかもしれないです.それやったら解析楽やね.

チェックバイト 17バイト目

正直1番の難関.わからん.

過去の機種であれば,

加算したのの下位8ビット

だったりするらしいです.

今回逆順多いしなあ...う〜ん

ってことでチェックバイトだけわかりません.

サンプルで信号いっぱいおいとくんで,閃いたら教えてください.

C4D36480000018506C0200000000000000B5
C4D36480000418506C0200000000000000B3
C4D36480000418506C820000000000000073
C4D36480000418506C4200000000000000F3
C4D36480000418506CC2000000000000000B
C4D36480000418506C0100000000000000B0
C4D36480000418506C820000000000000073
C4D36480000418506C1200000000000000AB
C4D36480000418506C0A00000000000000BB
C4D36480000418506C1A00000000000000A7
C4D36480000418706C02000000000000008B
C4D36480000418B06C02000000000000000B
C4D36480000410200C02000000000000008D
C4D36480000418E06C020000000000000053
C4D36480000418C06C020000000000000063

(2020/12/21)
まんま逆順でした.
コードをバイト単位でバイナリベースで逆順にすればわかりやすいです.

冷房 26 Auto Autoをバイト単位でバイナリベースで逆順にすると,

 0 1 2 3 4 5 6 7 8 9101112131415161718
23CB26010020180A364000000000000000CD23CB26010020180A364000000000000000CD

このうち重要な部分は5~9バイト目で,

 5  6  7  8  9
20 18 0A 36 40

電源オンなら20, オフなら00.
6, 8バイト目はモードコードの定数なのであまり興味なし.
7の0Aは,26度-16度=10→0x0A なのでそのままですね.
9バイト目も逆順なら,風速風向の変化に応じて+1されるようになります.

そして,逆順のコードのデータ部分の総和の下1バイトは見事CDになります.
単純な総和だと,桁上がりの方向が違うからダメなんですね

解析まとめ

スクリーンショット 2020-08-15 14.37.48.png

実際に送出する赤外線のコード

送信する設定データの解析はできましたので,これを使えるように,実際に送出する赤外線のコードを調べます.

記録した赤外線コードはこちらです.
データの数的に,めっちゃ長い2箇所は上で解析した設定データです.
設定データを始端と終端で囲みつつ,そのセットを2回繰り返しています.

8100 4500
0F00 3400 0F00 3400 0F00 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 3300 1000 3300 1000 1200 1000 3400 0F00 1300 1000 1200 1000 3400 0F00 3400 0F00 1300 1000 3400 0F00 3300 1000 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1200 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 3400 0F00 3400 0F00 1300 1000 1200 1000 1300 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1300 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 3400 0F00 3400 0F00 1200 1100 3300
1000 EE01
8200 4400
1000 3300 1000 3300 1000 1200 1000 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 3400 0F00 3400 0F00 1200 1100 3300 1000 1200 1000 1200 1100 3300 1000 3300 1000 1200 1000 3400 0F00 3400 0F00 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 1300 1000 1200 1000 3400 0F00 3400 0F00 1300 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 3300 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1000 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 1200 1100 1200 1000 3400 0F00 1200 1100 1200 1000 1200 1100 3300 1000 3300 1000 1200 1000 3400
1000 4205

Homebridge-cmdで使えるようにする

作成したリモコンエミュレータプログラム

RH191.swift
https://gist.github.com/on0z/521066482c1c63ac99619d8e3026ca05

これをラズパイ上でコンパイルすれば,実行ファイルができるので,これをHomebridge-cmdから呼べるようにします.

Cmd4Scripts/cmd4.sh

homebridge-cmdとRH191プログラムをつなぐ架け橋です.

私はhomebridgeから呼び出されるstate_cmdを全てCmd4Scripts/cmd4.shに集約しています.そんなに難しいことはしていませんし,今のところ天井灯とエアコンだけなので分量もありません.

以下エアコン部分の呼び出しコードです.

#!/bin/bash

cd `dirname $0`

# Notes
#
# 1) This script is called as defined by the config.json file as:
#     "state_cmd": "bash .homebridge/Cmd4Scripts/cmd4.sh"
#     $1 = 'Get'
#     $2 = <Device name>
#     $3 = <Characteristic>
#     $4 = <Device option>
#
# 2) For a set of On, the command issued would be:
#     bash $HOME/.homebridge/Cmd4Scripts/cmd4.sh Set Lightbulb1 On false
#         or
#     bash $HOME/.homebridge/Cmd4Scripts/cmd4.sh Set Lightbulb1 On true
#
# 3) For a Get of On, the command issued would be:
#     bash $HOME/.homebridge/Cmd4Scripts/cmd4.sh Get Lightbulb1 On
#
#     homebridge-cmd4 will interpret the result of false to be 1
#     and true to be 0 so either 0/1 or true/false can be returned.

LOGPATH="cmd4.log"

IRPYFILE="I2C0x52-IR/IR-remocon02-commandline.py"
CMDDIR="remocon_command"

STATEDIR="state"

LOCKFILE="lockfile"

echo "[$(date)] start \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}

# This is only here for the first run.
if [ ! -d "$STATEDIR" ]; then
    mkdir "$STATEDIR"
fi
if [ ! -d "$STATEDIR/$2" ]; then
    mkdir "$STATEDIR/$2"
fi
if [ ! -f "$STATEDIR/$2/$3" ]; then
    echo "0" > "$STATEDIR/$2/$3"
fi

if [ "$1" = "Get" ]; then
    # operation command here
    echo `cat $STATEDIR/$2/$3` >> ${LOGPATH}
    cat $STATEDIR/$2/$3
    rc=$?
    if [ "$rc" = "0" ]; then
        echo "\$1='$1' \$2='$2' \$3='$3' \$4='$4' done" >> ${LOGPATH}
        exit 0
    else
        echo "failed"
        echo "\$1='$1' \$2='$2' \$3='$3' \$4='$4' failed" >> ${LOGPATH}
        exit -1
    fi
fi

if [ "$1" = "Set" ]; then
    ln -s $0 $LOCKFILE &>>${LOGPATH}
    while [ $? -eq 1 ] 
    do
        ln -s $0 $LOCKFILE &>/dev/null
    done
    trap "rm $LOCKFILE; sleep 0.2" 0 1 2 3 15
    echo locked $$ >> ${LOGPATH}

    if [ "$2" = "MSZ-GV2219-W" ]; then
        ACTIVE=`cat $STATEDIR/$2/Active`
        MODE=`cat $STATEDIR/$2/TargetHeaterCoolerState`
        TEMPERATURE=`cat $STATEDIR/$2/HeatingThresholdTemperature`
        if [ "$3" = "Active" ]; then
            ACTIVE=$4
            if [ "$4" = "1" ]; then
                # オン
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then
                    case $MODE in
                        "0" ) 
                            python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                            echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                            echo 3 > $STATEDIR/$2/CurrentHeaterCoolerState
                            ;;
                        "1" )
                            python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                            echo run ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                            echo 2 > $STATEDIR/$2/CurrentHeaterCoolerState
                            ;;
                        "2" )
                            echo run ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                            python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                            echo 3 > $STATEDIR/$2/CurrentHeaterCoolerState
                            ;;
                        * )
                            echo ケースをスルーしました >> ${LOGPATH}
                            ;;
                    esac
                fi
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            elif [ "$4" = "0" ]; then
                # オフ
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi
                echo 0 > $STATEDIR/$2/CurrentHeaterCoolerState
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            fi
        elif [ "$3" = "TargetHeaterCoolerState" ]; then
            # CurrentHeaterCoolerState
            # 0 - "INACTIVE"
            # 1 = "IDLE"
            # 2 - "HEATING"
            # 3 = "COOLING"

            # TargetHeaterCoolerState
            # 0 - "Auto"
            # 1 = "HEATING"
            # 2 - "COOLING"
            MODE=$4
            if [ "$4" = "0" ]; then
                # 除湿 (自動)
                echo 3 > $STATEDIR/$2/CurrentHeaterCoolerState
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi

                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            elif [ "$4" = "1" ]; then
                # 暖房
                echo 2 > $STATEDIR/$2/CurrentHeaterCoolerState
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi

                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            elif [ "$4" = "2" ]; then
                # 冷房
                echo 3 > $STATEDIR/$2/CurrentHeaterCoolerState
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi

                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            fi
        elif [ "$3" = "HeatingThresholdTemperature" ]; then
            # 暖房温度 自動温度範囲ここまで温める 0-25

            # 0.5刻みは無理
            TEMPERATURE=${4%.*}

            # 実装してる範囲に限定
            if [ ${TEMPERATURE} -lt 16 ]; then
                TEMPERATURE=16
            elif [ ${TEMPERATURE} -gt 31 ]; then
                TEMPERATURE=31
            fi
            if [ ! $(cat $STATEDIR/$2/$3) = ${TEMPERATURE} ]; then
                if [ $(cat $STATEDIR/$2/TargetHeaterCoolerState) = "1" ]; then
                    #echo $TEMPERATURE > $STATEDIR/$2/CurrentTemperature
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi
            fi
            echo ${TEMPERATURE} | tee $STATEDIR/$2/$3 | tee -a ${LOGPATH}
            echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
            exit $?
        elif [ "$3" = "CoolingThresholdTemperature" ]; then
            # 冷房温度 自動温度範囲ここまで冷やす 10-35

            # 0.5刻みは無理
            TEMPERATURE=${4%.*}

            # 実装してる範囲に限定
            if [ ${TEMPERATURE} -lt 16 ]; then
                TEMPERATURE=16
            elif [ ${TEMPERATURE} -gt 31 ]; then
                TEMPERATURE=31
            fi
            if [ ! $(cat $STATEDIR/$2/$3) = ${TEMPERATURE} ]; then
                if [ $(cat $STATEDIR/$2/TargetHeaterCoolerState) = "2" ]; then
                    #echo ${TEMPERATURE} > $STATEDIR/$2/CurrentTemperature
                    python3 ${IRPYFILE} t `./RH191 ${ACTIVE} ${MODE} ${TEMPERATURE}`  >> ${LOGPATH} 2>&1
                    echo run ${ACTIVE} ${MODE} ${TEMPERATURE} >> ${LOGPATH}
                fi
            fi
            echo ${TEMPERATURE} | tee $STATEDIR/$2/$3 | tee -a ${LOGPATH}
            echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
            exit $?
        elif [ "$3" = "SwingMode" ]; then
            # スイング
            if [ "$4" = "0" ]; then
                # オフ
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then

                    : #something

                fi
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            elif [ "$4" = "1" ]; then
                # オン
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then

                    : #something

                fi
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            fi
        elif [ "$3" = "LockPhysicalControls" ]; then
            # チャイルドロック
            if [ "$4" = "0" ]; then
                # オフ
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then

                    : #something

                fi
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            elif [ "$4" = "1" ]; then
                # オン
                if [ ! $(cat $STATEDIR/$2/$3) = $4 ]; then

                    : #something

                fi
                echo $4 | tee $STATEDIR/$2/$3
                echo "[$(date)] done \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
                exit $?
            fi
        fi 
    fi
fi

echo "[$(date)] failed \$1='$1' \$2='$2' \$3='$3' \$4='$4' pid=$$" >> ${LOGPATH}
exit -1

細かいところが結構適当です.エラー処理とか.

動作させる

IMG_1862.png
IMG_1863.png

エアコンの機能と,HomeKitのアクセサリがうまく対応してない気がします.日本とアメリカで異なるんでしょうか.

後書き

そのうち勉強がてら学習リモコン基板のドライバないしカーネルモジュールを作りたいな と思ったり.温度センサーも付けたい.

久々の投稿でした.
以上

1
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
on0z
個人でSwiftを勉強してます。 自分用に下書きばっかり書いててあんまり公開しないので、フィードにあんまり浮上しない。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
1
Help us understand the problem. What is going on with this article?