Try scanning, analyzing, and transmitting the remote control of Mitsubishi's air conditioner with Raspberry Pi

Scan and send infrared remote control signals using GPIO of Raspberry Pi I wrote this article last time. The remote control for TVs and ceiling lights is simple, and it's easy because the same signal is always output when you press the same button, but the remote control for air conditioners is a little special and not so. In the case of an air conditioner, the remote control knows the current state of the air conditioner, and the operation when the button is pressed changes depending on the state. However, if you scan in the form of a signal when turning off the air conditioner, a signal when turning on cooling at 28 degrees, and a signal when turning on heating at 20 degrees, you can turn on / off cooling and heating. Will be. However, since it is a big deal, I would like to analyze the remote control so that the set temperature and wind direction can be adjusted freely.

The scan and send programs from the previous article will be used, so if you haven't read it, please read it first.

Remote controller

Remote.png

This is the remote control used for the analysis. The model name is NH122 205AL The guy who has an air conditioner in Kirigamine.

scan

First of all, I will scan the remote control of the air conditioner.

pi@raspberrypi:~/IR $ ./scan 20 > heat20
GPIO Pin Number : 20
Scanning begin
Scanning end
Output begin
Output end
pi@raspberrypi:~/IR $ cat heat20
3499	1681
460 	1257
462 	1256
460 	396
463 	396
462 	400
461 	1255
461 	399
459 	400
461 	1256
461 	1256
460 	400
(Omitted)
459 	1260
459 	400
458 	400
458 	13283
3470	1680
461 	1256
461 	1258
460 	399
460 	399
461 	399
459 	1256
461 	399
460 	399
459 	1259
460 	1255
461 	400
(Omitted)
458 	1264
457 	399
459 	399
458 	

Look at this to determine which format is being used. First of all, since it is not made by SONY, it is either NEC or Kyoseikyo. Infrared remote control communication format If you compare it with the site here, The lighting time 3499 of the first line, that is, the leader is about eight times the lighting time 460 when sending the data of the second and subsequent lines, so you can see that it is a home-made cooperative format. Also, there is a place where the lighting time is as long as 3470 on the way, so this remote control may be sending a 2-frame signal. But it doesn't seem to be repeating. However, I can't understand at all even if I look at this much, so let's write a program to convert this to hexadecimal.

analysis

Convert to hexadecimal

First, convert the scanned file to hexadecimal. In the home-made cooperative format, after the leader is transmitted, the first byte LSB to MSB are sent in 8-bit order. After that, the 2nd and 3rd bytes come in order, and the frame ends when the light is turned off for the last 8ms or more.

For the time being, ignore Trailer and Repeat and focus only on Leader and Data. The format of the remote controller has a modulation unit T, which determines the information sent by the lighting time and the extinguishing time being several times T. With Leader, lighting time is 8T and lighting time is 4T. When 0 is transmitted, the lighting time is T and the lighting time is T. When 1 is transmitted, the lighting time is T and the lighting time is 3T. Will be. So, based on the fact that T is 350 to 500, if the lighting time is long, it is determined by Leader, if the lighting time is about the same as the lighting time, it is 0, and if the lighting time is longer than the lighting time, it is 1 To do.

program

aeha.c


//
//  aeha.c
//  Copyright © 2018 Hiroki Kawakami. All rights reserved.
//
#include<stdio.h>
#include<stdlib.h>

int main() {
	
	int count, offset = -1;
	unsigned int on, off;
	unsigned char byte = 0;
			
	while(1) {
		count = scanf("%u%u", &on, &off);
		if (count < 2 || off == 0) {
			break;
		}
		
		if (on > 1500) {
			if (offset >= 0) printf("\n");
			offset = 0;
			byte = 0;
		} else {
			byte |= (off > on * 2) << offset++;
           		 
			if ((offset & 7) == 0) {
				printf("%02X ", byte);
				offset = 0;
				byte = 0;
			}
		}
	}
	printf("\n");

	return 0;
}

compile

$ gcc -o aeha aeha.c

Run

Convert the heat20 file created when scanning

pi@raspberrypi:~/IR $ cat heat20 | ./aeha
23 CB 26 01 00 20 48 04 30 6A 00 00 00 00 10 00 00 2B 
23 CB 26 01 00 20 48 04 30 6A 00 00 00 00 10 00 00 2B 

Now you can see the signal being sent by the remote control in hexadecimal. You can see that this remote control is sending the same frame twice.

analysis

Since I was able to see the signal in hexadecimal, I will scan the signal of the remote control in several modes and analyze it by comparing it, but I will scan it one by one and convert it to hexadecimal. It's a hassle to compare. But it's okay. It's not just for fun that I used standard I / O for file I / O. This allows you to connect the scan program and the hexadecimal conversion program directly with a pipe.

command

$ while true; do ./scan 20 2>/dev/null | ./aeha; done

./scan 20 | ./I can connect the scan program and the hexadecimal conversion program with aeha with a pipe, but the stderr output of the scan program is an obstacle, so 2>/dev/It is discarded as null. And, with this alone, the program will end after scanning once, so while true, the program is restarted in an infinite loop.


 By doing this, you can continuously scan the remote control signal and see it in hexadecimal value.
 When you're done, press Ctrl + C to exit.

### Run

 All you have to do is execute the command written above, send the remote control signals to the sensor one after another, and compare the displayed values.

pi@raspberrypi:~/IR $ while true; do ./scan 20 2>/dev/null | ./aeha; done 23 CB 26 01 00 20 48 04 30 40 00 00 00 00 10 00 00 01 23 CB 26 01 00 20 48 04 30 40 00 00 00 00 10 00 00 01 23 CB 26 01 00 20 48 03 30 40 00 00 00 00 10 00 00 00 23 CB 26 01 00 20 48 03 30 40 00 00 00 00 10 00 00 00 23 CB 26 01 00 20 48 02 30 40 00 00 00 00 10 00 00 23 CB 26 01 00 20 48 02 30 40 00 00 00 00 10 00 00 FF 23 CB 26 01 00 20 48 01 30 40 00 00 00 00 10 00 00 FE 23 CB 26 01 00 20 48 01 30 40 00 00 00 00 10 00 00 FE 23 CB 26 01 00 20 48 00 30 80 00 00 00 00 10 00 00 3D 23 CB 26 01 00 20 48 00 30 80 00 00 00 00 10 00 00 3D


 In rare cases, it may not be converted normally, but since this remote control sends the same signal twice, there is no problem in analysis.
 This is the result of pressing the button to lower the set temperature toward the sensor and inputting five signals from heating 20 degrees to heating 16 degrees in order.

 I think that the last 1 byte is used for error detection, so ignore it, and you can see that the value of the 7th byte (the 0th byte on the far left) has changed.

pi@raspberrypi:~/IR $ while true; do ./scan 20 2>/dev/null | ./aeha; done 23 CB 26 01 00 20 58 0C 36 40 00 00 00 00 10 00 00 1F 23 CB 26 01 00 20 58 0C 36 40 00 00 00 00 10 00 00 1F 23 CB 26 01 00 20 58 0D 36 40 00 00 00 00 10 00 00 20 23 CB 26 01 00 20 58 0D 36 40 00 00 00 00 10 00 00 20 23 CB 26 01 00 20 58 0E 36 40 00 00 00 00 10 00 00 21 23 CB 26 01 00 20 58 0E 36 40 00 00 00 00 10 00 00 21 23 CB 26 01 00 20 58 0F 36 80 00 00 00 00 10 00 00 62 23 CB 26 01 00 20 58 0F 36 80 00 00 00 00 10 00 00 62


 In addition, this is the result of pressing the button to raise the set temperature from cooling 28 degrees to cooling 31 degrees and inputting four signals in order, and it can be seen that the value of the 7th byte is also changing.

 Also, if you calculate the value of the 7th byte, you can see that it is -16 from the set temperature.
 Therefore, it was found that the set temperature is stored in the 7th byte.

 After that, in the same way for the operation mode, wind speed, wind direction, etc., just input signals that change only one condition in order and see the change in value.

### Last 1 byte

 There was an analysis of the remote control of National's air conditioner on the reference site. The remote control was in the home-made cooperative format, and the last 1 byte was the lower 8 bits of the sum of all the bytes. When I checked it with the remote control I am analyzing now, it was the lower 8 bits of the addition as in the reference site.

## Analysis result

 ![解析.png](https://qiita-image-store.s3.amazonaws.com/0/242402/8227486d-9575-6147-3c5d-33c8e94b4954.png)

 The signal when the timer is set is not analyzed. It's easier and more flexible to control it programmatically.
 When the wind area is 0, the value specified for the left and right winds is used. If specified here, the orientation specified in the wind area will be obtained without using the left and right wind values.

# Send

 Now that the analysis is complete, all you have to do is write a program that generates a signal according to the analysis results. The program that generates the signal was created with Node.js so that it can be easily operated via the Web later.

## program


#### **`mbac.js`**
```js

//
//  mbac.js
//  Copyright © 2018 Hiroki Kawakami. All rights reserved.
//

var bytes = [0x23, 0xcb, 0x26, 0x01, 0x00, 0x00, 0x58, 12, 0x32, 0x40, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0];

var aeha = function(T, bytes, repeats, interval) {
    var result = "";
    var i = 0;
    var length = bytes.length;
    while (true) {
        result += T * 8 + " " + T * 4 + "\n"; // Leader
        for (var j = 0; j < length; j++) {
            for (var k = 0; k < 8; k++) {
                if ((bytes[j] & (1 << k)) != 0) { // 1
                    result += T + " " + T * 3 + "\n";
                } else { // 0
                    result += T + " " + T + "\n";
                }
            }
        }
        if (++i >= repeats) {
            result += T;
            break;
        } else {
            result += T + " " + interval + "\n"; // Trailer
        }
    }
    return result;
}

var UpdateCheckByte = function() {
    var sum = 0;
    for (var i = 0; i < bytes.length - 1; i++) {
        sum += bytes[i];
    }
    bytes[bytes.length - 1] = sum & 0xff;
}

var SetPower = function(power) {
    if (power.toLowerCase !== undefined) {
        power = power.toLowerCase()
    }
    if (!power || power == "off" || power == "false") {
        bytes[5] = 0x00;
        savedState.power = false;
    } else {
        bytes[5] = 0x20;
        savedState.power = true;
    }
}

var SetMode = function(mode) { //Set mode (cool:Air conditioning, heat:heating, dry:Dehumidification, wind:Blower)
    mode = mode.toLowerCase();
    var byte = {"cool": 0x58, "heat": 0x48, "dry": 0x50, "wind": 0x38}[mode];
    if (byte === undefined) {
        console.log("Mode %s is not defined!", mode);
        return false;
    }
    SetPower(true);
    bytes[6] = byte;
    savedState.mode = mode;
    if (mode == "cool") {
        SetTemperature(savedState.coolTemperature);
        bytes[8] = (bytes[8] & 0xf0) | 0x6;
    } else if (mode == "heat") {
        SetTemperature(savedState.heatTemperature);
    } else if (mode == "dry") {
        SetDryIntensity(savedState.dryIntensity);
    }
    return true;
}

var SetTemperature = function(temperature) { //Set the set temperature (16~31)
    if (temperature < 16 || temperature > 31) {
        console.log("Temperature %d is out of range (16 ~ 31).", temperature);
        return false;
    }
    bytes[7] = temperature - 16;
    if (savedState.mode == "cool") {
        savedState.coolTemperature = temperature;
    } else if (savedState.mode == "heat") {
        savedState.heatTemperature = temperature;
    }
    return true;
}

var SetDryIntensity = function(intensity) { //Dehumidifying strength (high:strength, normal:standard, low:weak)
    intensity = intensity.toLowerCase();
    var byte = {"high": 0x0, "normal": 0x2, "low": 0x4}[intensity];
    if (byte === undefined) {
        console.log("Dry intensity %s is not defined!", intensity);
        return false;
    }
    bytes[8] = (bytes[8] & 0xf0) | byte;
    savedState.dryIntensity = intensity;
    return true;
}

var SetWindHorizontal = function(horizontal) { //Wind direction left and right (1:leftmost~ 3:Central~ 5:Rightmost, 6:rotation)
    if (horizontal <= 0 || horizontal > 6) {
        console.log("Horizontal wind direction %d is out of range (1 ~ 6).", horizontal);
        return false;
    }
    if (horizontal == 6) {
        horizontal = 0xc;
    }
    bytes[8] = (bytes[8] & 0xf) | (horizontal << 4);
    savedState.horizontal = horizontal;
    return true;
}

var SetWindVertical = function(vertical) { //Under wind improvement (0:Automatic, 1:Mogami~ 5:Bottom, 6:rotation)
    if (vertical < 0 || vertical > 6) {
        console.log("Vertical wind direction %d is out of range (0 ~ 6).", vertical);
        return false;
    }
    if (vertical == 6) {
        vertical = 7;
    }
    bytes[9] = (bytes[8] & 0b11000111) | (vertical << 3);
    savedState.vertical = vertical;
    return true;
}

var SetWindSpeed = function(speed) { //Wind speed (0:Automatic, 1:weak, 2:During ~, 3:strength, 4:powerful)
    if (speed < 0 || speed > 4) {
        console.log("Wind speed %d is out of range (0 ~ 4).", speed);
        return false;
    }
    var powerful = 0x00;
    if (speed == 4) {
        speed = 3;
        powerful = 0x10;
    }
    bytes[9] = (bytes[9] & 0b11111000) | speed;
    bytes[15] = powerful;
    savedState.speed = speed;
    return true;
}

var SetWindArea = function(area) { //Wind area (none:Use the left and right wind values, whole:The entire, left:Left half, right:Right half)
    var byte = {"none": 0x00, "whole": 0x8, "left": 0x40, "right": 0xc0}[area.toLowerCase()];
    if (byte === undefined) {
        console.log("Wind area %s is not defined!", area);
        return false;
    }
    bytes[13] = byte;
    savedState.area = area;
    return true;
}

var fs = require("fs");
var savedState = {
    "power": false, 
    "mode": "cool", 
    "coolTemperature": 28, 
    "heatTemperature": 20, 
    "dryIntensity": "normal", 
    "horizontal": 5, 
    "vertical": 0,
    "speed": 0,
    "area": "none"
};
try {
    savedState = JSON.parse(fs.readFileSync("mbac.sav", "utf8"));
} catch(error) {}

try {
    if (savedState.mode !== undefined) {
        SetMode(savedState.mode);
    }
    if (savedState.power !== undefined) {
        SetPower(savedState.power);
    }
    if (savedState.horizontal !== undefined) {
        SetWindHorizontal(savedState.horizontal);
    }
    if (savedState.vertical !== undefined) {
        SetWindVertical(savedState.vertical);
    }
    if (savedState.speed !== undefined) {
        SetWindSpeed(savedState.speed);
    }
    if (savedState.area !== undefined) {
        SetWindArea(savedState.area);
    }
} catch(error) {console.error(error)}

var SaveState = function() {
    fs.writeFile('mbac.sav', JSON.stringify(savedState));
}

if (require.main === module) {
    var i = 2;
    while (i < process.argv.length) {
        var key = process.argv[i];
        if (key == "-p" || key == "--power") {
            SetPower(process.argv[i + 1]);
            i += 2;
        } else if (key == "-m" || key == "--mode") {
            if (!SetMode(process.argv[i + 1])) {
                console.log("Invalid value of mode option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-t" || key == "--temperature") {
            if (!SetTemperature(process.argv[i + 1])) {
                console.log("Invalid value of temperature option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-d" || key == "--dry_intensity") {
            if (!SetDryIntensity(process.argv[i + 1])) {
                console.log("Invalid value of dry intensity option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-h" || key == "--horizontal") {
            if (!SetWindHorizontal(process.argv[i + 1])) {
                console.log("Invalid value of wind horizontal option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-v" || key == "--vertical") {
            if (!SetWindVertical(process.argv[i + 1])) {
                console.log("Invalid value of wind vertical option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-s" || key == "--speed") {
            if (!SetWindSpeed(process.argv[i + 1])) {
                console.log("Invalid value of wind speed option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else if (key == "-a" || key == "--area") {
            if (!SetWindArea(process.argv[i + 1])) {
                console.log("Invalid value of wind area option \"%s\"", process.argv[i + 1]);
                return;
            }
            i += 2;
        } else {
            console.log("Invalid option key \"%s\"", key);
            return;
        }
    }
    UpdateCheckByte();
    SaveState();
    var signal = aeha(430, bytes, 2, 13300);
    console.log(signal);
}

First, bytes is an array that stores the generated signals. The aeha function is a function that outputs the lighting / extinguishing time of the LED from the byte array according to the home-made cooperative format. The UpdateCheckByte function is a function that calculates and sets the check value at the very end of the signal. After that, there is a function that sets various states of the air conditioner. var fs = require("fs");From now on, it will be a program that restores the state of the previous air conditioner and saves the current state. By doing this, you can operate by specifying only the part you want to change without specifying all the air conditioner states each time.

if (require.main === module)The contents evaluate and execute command line arguments. Currently, I don't intend to use command line operations regularly, so the implementation here is appropriate.



## Run

 [Scan and send infrared remote control signals using GPIO of Raspberry Pi](https://qiita.com/Hiroki_Kawakami/items/3f7d332797d188dc9022)
 The send program uses the one in the above article as it is. Please see that for specific usage.

 Cooling 30 degrees

#### **`node mbac.js -m cool -t 30 | sudo ./send 18`**

Heating 20 degrees

node mbac.js -m heat -t 20 | sudo ./send 18



 Power off

#### **`node mbac.js -p off | sudo ./send 18`**

You can also operate using the following options

オプション.png

The program itself is simple, but I haven't tested it so there may be bugs.

Reference site

Analyzing remote control signal with Rasberry Pi 3 (10/1 remote control signal analysis result update) Infrared remote control communication format

Recommended Posts

Try scanning, analyzing, and transmitting the remote control of Mitsubishi's air conditioner with Raspberry Pi
Now, put "InfluxDB + Telegraf + Chronograf" in CentOS8 and try to control the temperature of multiple Raspberry pi4.
Build a NAS with DLNA function at the speed of a second with Raspberry Pi and Docker Compose
Graph the sensor information of Raspberry Pi in Java and check it with a web browser
Try using the Wii remote with Java
I want to control the start / stop of servers and databases with Alexa
Graph the sensor information of Raspberry Pi and prepare an environment that can be checked with a web browser
[Raspberry Pi] Try to link Apache2 and Tomcat