道阻且长

道阻且长

问君西游何时还,畏途巉岩不可攀。
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 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:

image

Infrared Receiver Connection#

image

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#

image

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.

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:

image

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, 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 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.

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

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

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