道阻且长

道阻且长

问君西游何时还,畏途巉岩不可攀。
twitter
github

Implementation of Pseudo-Smart Home Based on Raspberry Pi + Infrared Tube

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's just a matter of reaching out, it still interrupts my surfing experience online. Additionally, this unfortunate device cannot automatically remember the last wind speed, so after turning it on and off, I have to adjust the wind speed to my preferred level again, which increases my dissatisfaction day by day.

Finally, last week, after a long day at work, I came home to find that I had forgotten to turn it off before leaving, which made me feel even more regretful about the electricity bill. So I took a look 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 did not 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 the config. Most tutorials provide the location as /boot/config.txt, but that's not the case for me, so be sure to verify.

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 these operations, restart the Raspberry Pi to make the changes effective.

Hardware Connection#

According to the information, the specific pin definitions for the Raspberry Pi are as follows:

image

Infrared Receiver Connection#

image

Place the infrared receiver with the protruding part 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 to ground, and the positive to 3.3V power supply.

Infrared Transmitter Connection#

image

The transmitter is as shown in the picture, with the longer leg being positive, connected to GPIO17, and the shorter leg connected to ground.

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. When you press a button on the remote control, you will see 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 button (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

This defines two commands: 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#

While blindly copying the encoding is convenient, it always feels a bit unsatisfactory. So let's try to analyze the specific meanings.

Note: The following content is based on the analysis from “Midea R05D/BG Model Remote Control 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 speed (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 interchange 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 content of the two segments is completely identical. Each button press, whether to adjust wind speed or temperature, contains the above information.

Encoding Area Relationship#

Note: This is based solely on the specifications in the image, and does not discuss actual situations.

According to the specification, there is a rough relationship as follows:

image

Thus, we can write the data ABC, with the content as follows:

10110010 10011111 01000000

This 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 of X, it can be expanded to:

10110010 01001101 10011111 01100000 01000000 10111111

According to the high and low level rules above, the rules for LAA'BB'CC' are 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 the structure, we get 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#

At this point, after understanding the general rules, I thought I could write a script to automatically generate various combinations, right?

However, it cannot.

The materials I consulted were published in 2008, which is 15 years ago. Although some rules can be matched, many details have already changed.

The most significant difference is that my air conditioning can specifically adjust the wind speed level, from 1% to 100%. After capturing the infrared encoding for this part, 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 that is mutually inverse.

Thus, I used the command mode2 -m -d /dev/lirc1 | tee -a output.conf to record all the encodings with the same mode and temperature, varying the wind speed from 1 to 100, and used a script to convert them into binary sequences to look for patterns.

To put it simply, the conclusion is that for the additional data, there are roughly the following rules:

The first line of the additional block is fixed as 11010101.

The second line is the binary representation of the wind speed percentage (left-padded with 0).

The third line is filled with 0.

The fourth line is only 00000010 when the wind speed is 100, otherwise filled with 0.

The fifth line is filled with 0.

The sixth line starts filling from decimal 214 to 255, then from 1 to 99 filled to 56, with 100 being a special value corresponding to 59 (all actually converted to binary representation).

Since I only need the three parameters of temperature, mode, and wind 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 to conveniently control the air conditioning, not to study how to clone a remote control.

So let's continue to expand the idea:

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 create a minimal implementation:

// 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 file to the Raspberry Pi.

The content implemented by these two files is very simple: it exposes an interface on port 9997, concatenating different commands through GET requests and passing 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.

My Raspberry Pi's internal IP 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 startup. Please configure a proxy or try changing sources. 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, access http://<server ip>:10000 to enter the backend interface.

Configure Accessories#

Here, I will still implement the minimal requirement scenario, simplifying the complex operations of the air conditioning to just two—turn on & turn off. The turn on corresponds to 25 degrees, cooling, wind speed 1%, and turning off is simply turning off the air conditioning. More complex implementations can be explored independently.

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 used for turning on, turning off, and querying 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.

image

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 achieve this.

Of course, since we provide an HTTP API, there is no need for complex methods; just call it directly:

Turn on the air conditioning:

image

Turn off the air conditioning:

image

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 front-end application for both App and Web to control it. But suddenly I remembered the Apple ecosystem and changed the requirements midway. However, this also led to the inconvenience of operating on mobile (Android). Overall, pairing with HomeKit is quite comfortable, so it’s a trade-off.

Due to time constraints and the fact that I was again sitting at the airport at night, the code is not only rough but also follows the principle of just make it run. Further optimizations are needed in the future.

Currently, at least the following directions need improvement:

  • API layer, add an authentication system, and use Cloudflare-Tunnel to penetrate to the external network for true remote control instead of just local network control.

  • Infrared encoding aspect, complete more commonly used combinations, and try using related libs to send infrared encoding data directly instead of crudely calling external binaries.

  • HomeBridge layer, expand more functionalities and implement more refined control with automatic commands instead of treating it as a light bulb (laugh).

  • Scheduled tasks, automatically turn on/off at specified times: the Raspberry Pi has deployed Qinglong Panel, and simple scripts can achieve this.

  • Aesthetic adjustments: well, industrial style isn’t unusable, but I really don’t have that talent, so let it be.

Overall, although there were many twists and turns, and the final implementation was rushed and hasty, unexpectedly, the experience is still quite satisfactory, and it doesn't feel like a waste of time.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.