Cause#
The rented apartment provides Midea air conditioning, but unfortunately, it does not support smart home integration.
As the weather gets hotter, I find myself caught up in the subtle trivialities of turning the air conditioning on and off every day. Although most of the time it just takes a stretch of my hand, it still disrupts my internet surfing experience. Additionally, this unfortunate device cannot automatically remember the last wind speed, so after switching it on, I have to adjust the wind speed to my preferred level again, which only increases my dissatisfaction.
Finally, last week, after a day at work, I came back to find I had forgotten to turn it off before leaving, which made me feel even more pained about the electricity bill. So I looked at the Raspberry Pi sitting on the shelf collecting dust and decided to tinker with it.
Additional Materials#
- Infrared receiver (to record remote control infrared signal codes)
- Infrared transmitter (to send control signals)
- Dupont wires (for connections)
- Transistor (optional, said to enhance the signal, but I didn't use it)
Software Installation#
My device and system information are as follows:
ubuntu@ubuntu:~$ uname -a
Linux ubuntu 5.15.0-1027-raspi #29-Ubuntu SMP PREEMPT Mon Apr 3 10:12:21 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux
First, install the lirc
related packages:
sudo apt update
sudo apt install lirc
After installation, configure config
. Most tutorials provide the location as /boot/config.txt
, but it is not the case for me, so please be careful.
sudo vi /boot/firmware/config.txt
# Append the following content
dtoverlay=gpio-ir,gpio_pin=18
dtoverlay=gpio-ir-tx,gpio_pin=17
Modify the lirc configuration:
sudo vi /etc/lirc/lirc_options.conf
# Before modification
driver = devinput
device = auto
# After modification
driver = default
device = /dev/lirc0
After completing the operations, restart the Raspberry Pi to make it effective.
Hardware Connection#
According to the information, the specific pin definitions of the Raspberry Pi are as follows:
Infrared Receiver Connection#
Place the infrared receiver with the protrusion facing up. From left to right, the pins are data, negative, and positive. Connect the data pin to GPIO18 with a Dupont wire, the negative pin to the ground, and the positive pin to 3.3V power supply.
Infrared Transmitter Connection#
The situation of the transmitter is as shown in the figure. The longer leg is the positive, connected to GPIO17, and the shorter leg is connected to the ground.
Infrared Signal Related#
Signal Recording#
To control the air conditioning, the initial idea is to use the infrared tube to simulate the remote control button signals and then encode the control.
To simulate the signal, we need to record it first. Execute mode2 -m -d /dev/lirc1
in the terminal to start recording. At this point, pressing a button on the remote control will show output similar to the following on the screen:
ubuntu@ubuntu:/boot/firmware$ mode2 -m -d /dev/lirc1
Using driver default on device /dev/lirc1
Trying device: /dev/lirc1
Using device: /dev/lirc1
16777215
4475 4380 576 1580 574 497
579 1580 582 1576 571 500
584 498 576 1578 570 497
585 491 579 1575 580 498
580 496 580 1575 580 1585
570 496 581 1582 571 1575
583 493 586 1582 566 1582
573 1576 579 1575 579 1575
579 1581 573 497 579 1577
578 496 580 498 579 497
582 503 574 497 575 498
579 499 579 1575 578 498
580 498 584 491 580 497
578 498 578 507 579 1586
567 504 573 1581 588 1561
578 1575 583 1571 580 1576
579 1576 573 5184 4422 4409
573 1581 549 524 580 1577
576 1579 577 497 577 502
556 1600 553 568 505 543
558 1586 570 497 575 502
558 1597 550 1606 552 521
555 1629 549 1600 529 542
532 1605 552 1600 555 1624
530 1602 555 1625 550 1578
553 542 530 1627 554 520
530 545 533 545 528 546
533 542 549 529 531 547
533 1623 528 547 528 547
531 545 532 545 556 521
532 542 531 1631 528 545
529 1623 531 1624 532 1623
531 1625 555 1600 528 1626
532 5224 4401 4434 525 1627
529 1624 527 550 529 1626
522 545 531 1624 531 545
532 1624 530 545 532 1624
531 1624 531 545 532 545
532 1624 530 1630 525 545
531 545 531 548 529 545
531 546 531 547 532 552
535 538 531 548 530 544
532 545 529 548 530 543
532 547 532 549 529 547
531 538 531 553 523 546
531 546 530 546 534 543
530 546 532 553 528 543
534 543 531 546 530 1626
526 1624 530 1628 526 547
530 1625 536 1623 524 17394-pulse
Discard the first line 16777215
and the last 17394-pulse
, the remaining data is the information represented by the current key (for example, here it is cooling, 24 degrees, wind speed 1%).
Configuration Writing#
Based on this, we can write our own configuration:
sudo vi /etc/lirc/lircd.conf.d/aircon.lircd.conf
# The content is as follows:
begin remote
name aircon // Device name, can be modified
flags RAW_CODES
eps 30
aeps 100
gap 19991
begin raw_codes
name c_25_1
4469 4386 578 1574 581 496 580 1577 577 1574 580 497 584 494 578 1574 580 497 580 497 580 1575 580 496 580 498 578 1575 580 1574 580 496 580 1575 579 1574 580 499 579 1575 579 1575 579 1575 580 1575 581 1573 580 1574 579 498 580 1577 577 498 579 497 582 508 570 493 580 497 581 496 579 1575 579 1575 580 498 579 497 580 496 556 521 579 498 581 496 580 497 579 497 580 1575 579 1577 578 1577 577 1575 579 1576 580 1576 580 5174 4449 4383 581 1574 555 520 579 1576 579 1577 577 501 577 496 579 1576 555 521 579 498 556 1598 580 499 578 497 556 1598 556 1599 555 521 556 1601 554 1599 579 501 554 1597 555 1599 579 1576 555 1599 578 1576 579 1575 556 521 579 1577 577 499 577 522 556 497 555 544 532 545 532 521 582 1575 554 1599 556 545 531 522 578 497 578 499 556 521 556 521 578 498 555 522 554 1600 578 1578 578 1576 554 1599 555 1623 531 1623 532 5201 4423 4408 555 1600 555 1623 531 545 531 1623 532 547 531 1622 555 521 532 1623 533 544 532 1623 532 1623 532 545 531 545 556 1599 531 1623 531 545 532 545 557 520 532 545 531 545 532 545 556 520 532 546 531 545 531 545 531 545 532 545 531 545 531 546 531 546 531 545 532 545 532 546 531 546 556 521 531 549 529 546 529 545 533 544 531 545 532 545 532 544 531 1623 532 1623 531 1629 526 546 531 1624 531 1623 531
name off
4465 4388 578 1574 580 496 583 1572 580 1577 580 496 578 497 580 1575 580 496 581 496 580 1574 581 496 581 496 581 1577 578 1574 581 502 574 1575 579 496 584 1571 581 1574 581 1574 581 1575 580 496 580 1575 580 1574 580 1575 583 494 582 495 580 497 580 498 578 1574 580 497 580 497 580 1575 580 1576 579 1574 580 498 582 495 580 497 581 497 580 499 578 497 581 497 578 497 582 1572 580 1576 581 1574 579 1576 579 1575 580 5179 4450 4382 579 1575 579 500 577 1575 580 1576 578 497 556 521 580 1577 580 496 580 497 579 1579 576 497 579 506 574 1576 552 1599 581 496 579 1576 579 498 581 1575 578 1579 576 1576 579 1577 578 498 579 1575 580 1575 579 1577 555 521 556 521 579 498 556 524 578 1576 577 498 579 498 556 1599 556 1599 579 1576 578 498 579 499 555 522 555 521 556 521 556 522 578 497 556 522 558 1597 579 1575 558 1597 555 1602 576 1576 579
end raw_codes
end remote
Here, two commands are defined: c_25_1 represents cooling, 25 degrees, 1% wind speed; off represents turning off the air conditioning.
Save the file and continue to execute sudo systemctl restart lircd
to restart the service and make the configuration effective.
At this point, you can send commands in the terminal: irsend send_once aircon c_25_1
to control the air conditioning.
Encoding Understanding#
Blindly copying the encoding is convenient, but it always feels unsatisfactory. So let's try to analyze the specific meaning.
Note: The following content is based on the analysis from “Midea R05D/BG Model Remote Control Electrical Function Specification”. Interested readers can read the original text.
Specific Structure#
From the collected information, it can be seen that when only involving temperature
, mode
, and wind strength (not specific wind speed)
, the encoding structure is roughly:
LAA'BB'CC'SLAA'BB'CC'Z
Where we can set:
L is the lead code, corresponding to the signal [4500, 4500]
S is the separator code, corresponding to the signal [540, 5200]
Z is the end identifier, corresponding to the signal 550
A is a fixed identification code, corresponding to the binary sequence 10110010
B is related to wind speed, C is related to temperature and mode
Moreover, there is an interchangeable relationship between the binary sequence and the signal encoding: 1 represents high level, corresponding to the signal [540, 1600]
, and 0 represents low level, corresponding to the signal: [550, 550]
Thus, after conversion, the above A (identification code) corresponds to the following data: 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550
.
The data is divided into two segments, excluding the lead code and separator code, the contents of the two segments are completely identical. Each time a button is pressed, whether to adjust the wind speed or temperature, it contains the above information.
Encoding Area Relationship#
Note: This section only discusses the specifications in the figure, without discussing the actual situation.
According to the specification, there is roughly the following relationship:
Thus, we can write data ABC
, with the content as follows:
10110010 10011111 01000000
It can be translated to:
Fixed identification code Low wind Cooling 24 degrees
Since the data structure is AA'BB'CC'
, where X' represents the inverse code of X, it can be expanded to:
10110010 01001101 10011111 01100000 01000000 10111111
According to the high and low level rules, the rules for LAA'BB'CC'
can be obtained as follows:
4500 4500 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 540 1600
Continuing to expand according to the structure, we obtain the rules for LAA'BB'CC'SLAA'BB'CC'Z
:
4500 4500 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 540 1600 540 5200 4500 4500 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 540 1600 550 550 540 1600 540 1600 550 550 550 550 540 1600 550 550 550 550 550 550 540 1600 550 550 550 550 540 1600 540 1600 550 550 540 1600 550 550 540 1600 550 550 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 550 550 550 550 540 1600 540 1600 550 550 550 550 550 550 550 550 550 550 550 550 550 550 540 1600 550 550 550 550 550 550 550 550 540 5200 4500 4500 550 550 550 550 550 550 540 1600 550 550 540 1600 540 1600 540 1600 540 1600 540 1600 540 1600 550
Advanced Understanding#
By now, having understood the general rules, I thought I could write a script to automatically generate various combinations.
However, this is not possible.
The materials I consulted were published in 2008, which is 15 years ago. Although some rules can be matched, many details have changed.
The most significant difference is that my air conditioning can adjust the wind speed level specifically, from 1% to 100%. After capturing this part of the infrared encoding, I found that its format is as follows:
LAA'BB'CC'SLAA'BB'CC'SLDEFNNGZ
It can be seen that the data length has expanded from two ends to three segments, and the third segment is no longer the AA'BB'CC'
structure of mutual inverse codes.
Thus, I used the command mode2 -m -d /dev/lirc1 | tee -a output.conf
to record all the encodings for wind speeds from 1 to 100 under the same mode and temperature, and used a script to convert them into binary sequences to find patterns.
To put it simply, the conclusion is that for the additional data, there are roughly the following rules:
The first line of the extra block is fixed 11010101
The second line is the binary representation of the wind speed percentage (left-padded with 0
)
The third line is fixed padding 0
The fourth line is 00000010
only when the wind speed is 100
; otherwise, it is padded with 0
The fifth line is fixed padding 0
The sixth line starts filling from the decimal 214
, and when it reaches 255
, it fills from 1
to 99
, and 100
is a special value corresponding to 59
(all converted to binary representation)
Since I only need the three parameters of
temperature
,mode
, andwind speed
, and due to the lack of information, I did not continue to delve deeper.
At this point, I can barely write a script to generate different command combinations for 1~100
wind speed, 17~30
temperature, and cooling/heating
mode.
Automation Exploration#
Although I have researched a lot, the actual progress towards my needs is not ideal. After all, what I need is convenient control of the air conditioning, not to study how to clone an air conditioning remote control.
So let's continue to expand our thinking:
API Call#
To achieve a certain degree of intelligence/automation, the simplest and easiest solution is to expose an API interface for third-party services to call.
Due to time constraints, I will use Golang to implement a minimal version:
// main.go file
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
var isON = false
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/aircon/:mode/:temp/:flow", func(c *gin.Context) {
mode := c.Params.ByName("mode")
temp := c.Params.ByName("temp")
flow := c.Params.ByName("flow")
cmd := mode + "_" + temp + "_" + flow
isON = true
IrsenAircon(cmd)
c.JSON(http.StatusOK, gin.H{"cmd": cmd})
})
r.GET("/aircon/off", func(c *gin.Context) {
IrsenAircon("off")
isON = false
c.JSON(http.StatusOK, gin.H{"cmd": "off"})
})
r.GET("/aircon/status", func(c *gin.Context) {
var result string
if isON {
result = "1"
} else {
result = "0"
}
c.String(http.StatusOK, result)
})
return r
}
func main() {
IrsenAircon("off")
r := setupRouter()
r.Run(":9997")
}
// aircon.go file
package main
import (
"fmt"
"log"
"os/exec"
)
func IrsenAircon(cmd string) {
cmdOutput, err := exec.Command("irsend", "send_once", "aircon", cmd).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", cmdOutput)
}
Compile the above code into a binary that can run on Raspberry Pi 4B:
export GOARCH=arm64
export GOROOT_BOOTSTRAP=/usr/local/go
export GOOS=linux
go build
Upload the compiled binary to the Raspberry Pi.
The content implemented by these two files is very simple: it exposes an interface on port 9997, constructs different commands through GET requests, and passes them to the irsend
binary file in the system to send the corresponding infrared signals.
It should be noted that when a signal containing the current scene information is sent, the air conditioning will be directly turned on. Therefore, there is no separate turn on
command, but there will be a fixed turn off
command.
The internal IP of my Raspberry Pi is 192.168.2.224
. Based on the interface, there are three command sets:
# Set air conditioning to 25 degrees, cooling, wind speed 1%
➜ Desktop curl http://192.168.2.224:9997/aircon/c/25/1
{"cmd":"c_25_1"}⏎
# Turn off the air conditioning
➜ Desktop curl http://192.168.2.224:9997/aircon/off
{"cmd":"off"}⏎
# Query status (0 means off, 1 means on)
➜ Desktop curl http://192.168.2.224:9997/aircon/status
0⏎
The air conditioning cannot callback its current status to the Raspberry Pi, and we cannot actively probe its current operating information. Therefore, once the signal emission operation is executed, we assume it is successful and update the internal maintained switch status. To maintain the accuracy of the status as much as possible, the program first executes a turn-off operation to calibrate.
Finally, use pm2 to manage the corresponding process:
pm2 --name lirc-web start /mnt/sda/Downloads/lirc-web
# Follow the prompts to set up auto-start
pm2 startup
sudo env PATH=$PATH:/home/ubuntu/.nvm/versions/node/v18.13.0/bin /home/ubuntu/.nvm/versions/node/v18.13.0/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu
"Smart Home"#
Although I have implemented a programming interface, it does not mean that all needs are solved. If controlling the air conditioning relies on accessing a URL in the browser
or initiating curl in the command line
, it is not much better than using a remote control.
Since macOS and iPadOS come with a Home
app that can perform smart home linkage control, as long as the above rudimentary interface is integrated into HomeKit
, the convenience can be further enhanced.
HomeBridge#
We use the HomeBridge
application to complete this step. Its official description is as follows:
Homebridge allows you to integrate smart home devices that do not support HomeKit. There are over 2,000 Homebridge plugins that support thousands of different smart accessories.
Without further ado, directly use Docker-Compose
:
cd ~/docker/homebridge/
vi docker-compose.yml
# Write the following content:
version: '3'
services:
homebridge:
image: oznu/homebridge:ubuntu
container_name: homebridge
restart: always
network_mode: host
environment:
- HOMEBRIDGE_CONFIG_UI_PORT=10000
volumes:
- homebridge:/homebridge
volumes:
homebridge:
After saving, execute docker-compose up -d
and wait for the image to finish pulling, and it will automatically start running.
This application requires access to the NPM repository during installation and operation. If your network has issues and access fails, it will prevent the service from starting. Please configure a proxy or try changing the source. However, even if the service fails to start, the UI access is not affected, so you can still check the logs in the browser.
After a while, visit http://<server ip>:10000
to enter the backend interface.
Accessory Configuration#
Here, we still implement the minimal requirement scenario, simplifying the complex operations of the air conditioning to two—turn on & turn off. The turn-on corresponds to
25 degrees, cooling, wind speed 1%
, and the turn-off is to turn off the air conditioning. More complex implementations can be explored by yourself.
To call the HTTP interface, we need to install the plugin: homebridge-http
.
After installation, configure as follows in the backend:
{
"bridge": {
"name": "Homebridge",
"username": "1D:42:45:4B:E4:A4",
"port": 51177,
"pin": "XXX-XX-XXX",
"advertiser": "bonjour-hap"
},
"accessories": [
{
"accessory": "Http",
"name": "Media Aircon",
"switchHandling": "realtime",
"on_url": "http://192.168.2.224:9997/aircon/c/25/1",
"off_url": "http://192.168.2.224:9997/aircon/off",
"status_url": "http://192.168.2.224:9997/aircon/status"
}
],
"platforms": [
{
"name": "Config",
"port": 10000,
"platform": "config"
}
]
}
The main part is the accessories
field, where a new object is added. The on_url
, off_url
, and status_url
represent the interfaces for turning on, turning off, and querying the status, respectively. Of course, the plugin supports more parameters, which can be found in its GitHub repository documentation.
After completing the configuration, restart the HomeBridge service, open the Home app, and scan the QR code displayed in the backend to discover the device.
At this point, you can normally obtain the status and perform on/off operations.
Voice Control#
So can we take it a step further? For example, when lying in bed and not wanting to lift a hand, voice control would be very useful.
It's simple; you can use Shortcuts
to accomplish this.
Of course, since we provide an HTTP API, there is no need for complex methods; you can call it directly:
Turn on the air conditioning:
Turn off the air conditioning:
At this point, you can directly tell Siri the name of your shortcut to use it.
Reflection and Summary#
Originally, I planned to use Flutter to implement a set of App+Web front-end applications for control. However, I suddenly remembered the Apple ecosystem, so I changed the requirements midway. However, this also led to the inconvenience of operating on mobile (Android). Overall, pairing with HomeKit is quite comfortable, which is a trade-off.
Due to time constraints and the fact that I have been once again waiting at the airport at night, the code is not only rough but also follows the principle of just getting it to run. In the future, at least I need to further optimize it.
Currently, at least the following directions need improvement:
-
API layer, add an authentication system, combined with
Cloudflare-Tunnel
to penetrate to the external network, achieving true remote control instead of local network control. -
Infrared encoding aspect, complete more commonly used combinations, and try to use related libs to directly send infrared encoding data instead of crudely calling external binaries.
-
HomeBridge layer, need to expand more functions and implement more refined control with automatic commands instead of treating it as a light bulb (laughs).
-
Scheduled tasks, automatically turn on/off at specified times: the Raspberry Pi has deployed the
Qinglong panel
, and simple scripts can accomplish this. -
Appearance correction: uh, industrial style is not unusable, but I really don't have that talent, so forget it.
Overall, although there were many twists and turns, the final implementation was also rushed and hasty, but unexpectedly, the experience is still quite satisfactory, so it is not a waste of time.