LoginSignup
2
2

More than 3 years have passed since last update.

【Raspberry Pi】エアコンをPHPから操作

Last updated at Posted at 2020-06-10

こちらの記事を自分なりに咀嚼した内容を書いていきます。
Raspberry Piで三菱のエアコンのリモコンをスキャン・解析・送信してみる

環境

  • Macbook
  • Raspberry Pi3 ModelB
  • 三菱 霧ヶ峰(エアコン)

実際の操作

赤外線の受信をしたあと(赤外線受信は以前のこちらの記事の方法でやります)のファイルの中を除くとこの様になっています。

codes
{"aircon:on": [3440,1720,430,1290,430,1290,430,430,430,430,430,430,430,1290,430,430,430,430,430,1290,430,1290,430,430,430,1290,430,430,430,430,430,1290,430,1290,430,430,430,1290,430,1290,430,430,430,430,430,1290,430,430,430,430,430,1290,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,1290,430,1290,430,430,430,1290,430,430,430,430,430,1290,430,1290,430,1290,430,430,430,430,430,430,430,430,430,430,430,1290,430,1290,430,430,430,1290,430,430,430,430,430,430,430,1290,430,430,430,430,430,1290,430,430,430,430,430,1290,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,1290,430,430,430,1290,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,1290,430,1290,430,1290,430,430,430,1290,430,1290,430,1290,430]}

説明しやすくするために少し加工します。

3469  1697  
428  1290
428  1290
428  430
428  430
428  430
428  430
428  430
428  430
428  430

(中略)

428  430
428  1290
428  430
428  1290
428  430
428  430
428  430
428  430
428  

左の列は赤外線が点灯している時間
右の列は赤外線が消灯している時間です。

一番最後が点灯時間だけで終わっているのは、、消灯しっぱなしのため記録されません。

一番上の34691697はLeaderと呼ばれ
下記を参考に3469≒8×1697が成り立つため、このリモコンは家製協フォーマットであると言えます。
赤外線リモコンの通信フォーマット

解析結果の表

解析結果とプログラムを参考にPHPのプログラムにしていきます。

解析結果https___qiita-image-store.s3.amazonaws.com_0_242402_8227486d-9575-6147-3c5d-33c8e94b4954.png

出典:Raspberry Piで三菱のエアコンのリモコンをスキャン・解析・送信してみる

2020年8月10日 追記-------------------------------
この表ですが、3文字で書かれているところ、例えば0x2や0x6等はそれぞれ0x02や0x06を表しているようです。
追記終わり-------------------------------

これに追加して内部クリーンに関する命令も発見したので書いておきます。

バイト 機能 設定値
14バイト目 内部クリーン 入:14 切:16

内部クリーンを有効にしておくとエアコンをオフにした後、エアコン側で内部クリーンなるものをするのですが、なんだか匂いがきついんで切:16を固定値として動かしていきます。

プログラム

Raspberry Piで三菱のエアコンのリモコンをスキャン・解析・送信してみるでC言語で書かれていたプログラムをPHPに書き換えたのがこちらです。
まだまだ修正すべき点があるため、今後も更新予定です。

exec.php
<?php
const FILE_PATH='/var/www/html/aircon/php/';
if (file_exists(FILE_PATH.'savedata')) {
    $bytes=unserialize(file_get_contents(FILE_PATH.'savedata'));

    $bytes[17]=0;
} else {
    $bytes=[
    '0'=>35, //固定値
    '1'=>203,//固定値
    '2'=>38, //固定値
    '3'=>1,  //固定値
    '4'=>0,  //固定値
    '5'=>32, //電源  入:32 切:0
    '6'=>88, //モード 冷房(体感入):88 冷房(体感切):24 暖房(体感入):72 暖房(体感切):8 除湿(体感入):80 除湿(体感切):16 送風:56
    '7'=>10, //設定温度  設定温度-16
    '8'=>22, //風左右、除湿強度、
    '9'=>73, //動作音、風上下、風速
    '10'=>0, //固定値
    '11'=>0, //固定値
    '12'=>0, //固定値
    '13'=>0, //風エリア
    '14'=>16,//内部クリーン 入:14 切:16
    '15'=>0, //風速パワフル
    '16'=>0, //固定値
    '17'=>0  //チェック
  ];
}
function aeha($T, $bytes, $repeats, $interval)
{
    $bytes=updateCheck($bytes);
    $i=0;
    $length=count($bytes);
    $file=fopen(FILE_PATH.'codes', 'w');
    flock($file, LOCK_EX);
    fwrite($file, '{"aircon:on": [');
    while (1) {
        fwrite($file, ($T*8).",".($T*4).",");
        for ($j=0; $j < $length; $j++) {
            for ($k=0; $k < 8 ; $k++) {
                if ($bytes[$j] & (1 << $k)) {
                    fwrite($file, $T.",".($T*3).",");
                } else {
                    fwrite($file, $T.",".$T.",");
                }
            }
        }
        if (++$i >= $repeats) {
            fwrite($file, $T);
            break;
        } else {
            fwrite($file, $T." ".$interval);
        }
    }
    fwrite($file, ']}');
    flock($file, LOCK_UN);
    fclose($file);
    shell_exec('python3 '.FILE_PATH.'irrp.py -p -g25 -f '.FILE_PATH.'codes aircon:on');
    if ($bytes[5]==32) {
        SaveData($bytes);
    }
}

function SaveData($bytes)
{
    $s=serialize($bytes);
    file_put_contents(FILE_PATH.'savedata', $s);
}

function GetOption()
{
    $bytes=unserialize(file_get_contents(FILE_PATH.'savedata'));
    $mode=[
      88=>'冷房(体感入)',
      24=>'冷房(体感切)',
      72=>'暖房(体感入)',
      8=>'暖房(体感切)',
      80=>'除湿(体感入)',
      16=>'除湿(体感切)',
      56=>'送風'][$bytes[6]];
    $speed=[
      '自動',
      '弱',
      '中',
      '強'][$bytes[9]&7];
    $area=[
      0=>'風左右',
      128=>'全体',
      64=>'左半分',
      192=>'右半分'][$bytes[13]];
    $dryintensity=[
      0=>'強',
      2=>'標準',
      4=>'弱',
      6=>'冷房'][$bytes[8]&15];

    print "モード:{$mode}\n";
    print "設定温度:".($bytes[7]+16)."℃\n";
    print "除湿強度:{$dryintensity}\n";
    print "風速:{$speed}\n";
    print "風エリア:{$area}\n";
}

function SetPower($power)
{
    global $bytes;
    if (!$power || $power === "off" || $power === "false") {
        $bytes[5] = 0;
    } else {
        $bytes[5] = 32;
    }
}

function SetMode($mode)
{// モードをセット(cool: 冷房, heat: 暖房, dry: 除湿, wind: 送風)
    global $bytes;
    $b=["cool"=> 88, "heat"=> 72, "dry"=> 80, "wind"=> 56][$mode];
    if (!$b) {
        print "Mode {$mode} is not defined!\n";
        return false;
    }
    SetPower(true);
    $bytes[6]=$b;
    return true;
}

function SetDryIntensity($intensity)
{ // 除湿強度(high: 強, normal: 標準, low: 弱)
    global $bytes;
    $b = ["high"=> 0, "normal"=> 32, "low"=> 64][$intensity];
    if (!$b) {
        print "Dry intensity {$intensity} is not defined!" ;
        return false;
    }
    $bytes[8] = ($bytes[8] & 240) | $b;
    return true;
}

function SetTemperature($temperature)
{// 設定温度をセット(16~31)
    global $bytes;
    if ($temperature < 16 || $temperature > 31) {
        print "Temperature {$temperature} is out of range (16 ~ 31).";
        return false;
    }
    $bytes[7] = $temperature - 16;
    return true;
}

function SetWindSpeed($speed)
{ // 風速(0: 自動, 1: 弱, 2: 中, 3: 強, 4: パワフル)
    global $bytes;
    if ($speed < 0 || $speed > 4) {
        print "Wind speed {$speed} is out of range (0 ~ 4).";
        return false;
    }
    $powerful = 0;
    if ($speed == 4) {
        $speed = 3;
        $powerful = 16;
    }
    $bytes[9] = ($bytes[9] & 248) | $speed;
    $bytes[15] = $powerful;
    return true;
}

function SetWindVertical($vertical)
{ // 風向上下(0: 自動, 1: 最上 ~ 5: 最下, 6: 回転)
    global $bytes;
    if ($vertical < 0 || $vertical > 6) {
        print "Vertical wind direction {$vertical} is out of range (0 ~ 6)." ;
        return false;
    }
    if ($vertical == 6) {
        $vertical = 7;
    }
    $bytes[9] = ($bytes[8] & 199) | ($vertical << 3);
    return true;
}

function SetWindArea($area)
{ // 風エリア(none: 風左右の値を利用, whole: 全体, left: 左半分, right: 右半分)
    global $bytes;
    $b = ["none"=> 0, "whole"=> 128, "left"=> 64, "right"=>192 ][$area];
    if (!$b) {
        print "Wind area {$area} is not defined!";
        return false;
    }
    $bytes[13] = $b;
    return true;
}

function SetSysVolume($vol)
{//音(なし:0, あり:1)

    global $bytes;
    if ($vol=='0' || $vol =='1') {
        $bytes[9] = ($bytes[9] & 63) | ($vol << 6);
        return true;
    } else {
        print "0 or 1 (0:mute , 1:sound)" ;
        return false;
    }
}

function updateCheck($bytes)
{
    $sum=0;
    for ($i=0; $i <count($bytes)-1 ; $i++) {
        $sum+=$bytes[$i];
    }
    $bytes[17]=$sum&255;
    return $bytes;
}

if (@$_SERVER["REQUEST_METHOD"] == "POST") {
    switch ($_POST['data']) {
      case 'on':
        break;
      case 'off':
        SetPower('off');
        break;
      case 'up':
        SetTemperature($bytes[7]+17);
        break;
      case 'down':
        SetTemperature($bytes[7]+15);
        break;
      case 'get':
        GetOption();
        exit;
        break;
      default:
        exit;
        break;
  }
} elseif (count($argv)==1) {
    $bytes=unserialize(file_get_contents(FILE_PATH.'savedata'));
} else {
    for ($i=1; $i<count($argv);) {
        if ($argv[$i] == "-p" || $argv[$i] == "--power") {
            SetPower($argv[$i + 1]);
            $i+=2;
        } elseif ($argv[$i] == "-m" || $argv[$i] == "--mode") {
            if (!SetMode($argv[$i + 1])) {
                print "Invalid value of mode option {$argv[i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-t" || $argv[$i] == "--temperature") {
            if (!SetTemperature($argv[$i + 1])) {
                print "Invalid value of temperature option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-d" || $argv[$i] == "--dry_intensity") {
            if (!SetDryIntensity($argv[$i + 1])) {
                print "Invalid value of dry intensity option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-h" || $argv[$i] == "--horizontal") {
            if (!SetWindHorizontal($argv[$i + 1])) {
                print "Invalid value of wind horizontal option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-v" || $argv[$i] == "--vertical") {
            if (!SetWindVertical($argv[$i + 1])) {
                print "Invalid value of wind vertical option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-s" || $argv[$i] == "--speed") {
            if (!SetWindSpeed($argv[$i + 1])) {
                print "Invalid value of wind speed option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-a" || $argv[$i] == "--area") {
            if (!SetWindArea($argv[$i + 1])) {
                print "Invalid value of wind area option {$argv[$i + 1]}";
            }
            $i+=2;
        } elseif ($argv[$i] == "-g" || $argv[$i] == "--get") {
            GetOption();
            exit;
            $i++;
        } elseif ($argv[$i] == "-vo" || $argv[$i] == "--volume") {
            if (!SetSysVolume($argv[$i + 1])) {
                print "IInvalid value of system volume option {$argv[$i + 1]}";
                var_dump(SetSysVolume($argv[$i + 1]));
            }
            $i+=2;
        } else {
            print "Invalid option key \"{$argv[$i]}\"";
            $i+=2;
            exit;
        }
    }
}



aeha(430, $bytes, 1, 13300);
$_POST=[];

ちょっとだけ解説

FILE_PATHにこのコードを配置したパスを記載します。

FILE_PATH.'savedata'は赤外線のデータを配列にしたものをシリアライズ化したファイルで、2回め以降の実行の際はこのデータを使用し、初回使用時でFILE_PATH.'savedata'がない場合は適当なデータを指定しています。

aeha関数は以下の役割を担っています。
- 解析したデータを赤外線コードに変換し、赤外線送信用コードファイルcodesを作成
- 赤外線の送信

updateCheck$bytes$bytes[17]以外のすべての値の合計値を$bytes[17]に格納しています。

赤外線データは以下の2つのペアで構成されています。
- 430 430
- 430 1290

これはつまりこういうことです。

ペア 意味
点灯時間 消灯時間 0
点灯時間 3✕消灯時間 1

つまりは信号は「0」と「1」の信号が送られいるわけです。

赤外線コード一部抜粋を0と1で見てみるとこうなります。

赤外線コード 意味
428 1290 1
428 1290 1
428 430 0
428 430 0
428 430 0
428 430 0
428 430 0
428 430 0

これからのコードが8ビットずつ17回送られています。

コードのはじめはLeaderと呼ばれ、
8✕点灯時間と4✕消灯時間で構成されています。以降で430と1290の組み合わせを生成していきます。

流れとしては、$bytesのそれぞれの値を8回ビットシフトさせ0のときは[430 430]、1のときは[430 1290]を生成していきます。

そうして送信用コードcodesを作成し、shell_execで実行しその後SaveDataで送信したデータを記録しておきます。
この記録に関してはデータベースを使用する予定です。

後はirrp.pyに送信命令をして完了って感じです。

実行方法

実際には使い方は以下のようにします。

$  php exec.php -m cool -t 24 -p on -s 4

上記だと、冷房、24℃、風速パワフルで運転するという命令になります。

2
2
0

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
2
2