上一篇中已经能够成功红外控制空调,剩下的就是把它放到HomeAssistant中。

MQTT HVAC

最简单的方法是作为开关加入,然后运行命令,但是这样子只能控制制冷或制热,或者要加入很多开关。这样就很麻烦了,我还希望能够加入HomeKit中控制。

好在HomeAssistant有个东西叫MQTT HVAC,也是属于climate,可以直接接入HomeKit。

MQTT协议就相当于有两方在通信,一个是消息的接收者,一个是消息的发送者。当我们改变空调模式、温度时,消息发送到接收者,接收者去执行shell命令发送红外。大概原理就是这样,所以我们需要的是跑一个接收者的MQTT客户端。

这里面url需要填写HomeAssistant密码,mode和temp两个主题可以改成自己想要的名字, client.on传入的回调函数就是对发过来的消息做出反应。因为HomeAssistant或HomeKit中打开空调需要先选择制冷、制热、自动,if (payload === 'heat')这几个if就是对于模式选择的反应,比如选择了制热就发送'制热21度'的红外码,spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_21']),'aircon'是lirc配置的名字,'heat_21'是红外码的名字,以此类推。

#!/usr/bin/env node
var mqtt = require('mqtt'); 

var mode = 'mitsubishi/ac/mode/set'; 
var temp = 'mitsubishi/ac/temperature/set';
var url = 'mqtt://homeassistant:你的密码@127.0.0.1';
var spawn = require('child_process').spawn;
var mode0;

console.log(url);

var client = mqtt.connect(url); 

client.subscribe(mode); 
client.subscribe(temp);

client.on('message', (topic, message) => {

  const payload = message.toString()
  
  if (payload === 'heat') {
    console.log(`Now Mode '${payload}'`)
    mode0 = "heat";
    spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_21'])
  }
  else if (payload === 'cool') {
    console.log(`Now Mode '${payload}'`)
    mode0 = "cool"
    spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_28'])
  }
  else if (payload === 'auto') {
    console.log(`Now Mode '${payload}'`)
    mode0 = "auto"
    spawn('irsend', ['SEND_ONCE', 'aircon', 'off'])
  }
  else if (payload === 'off') {
    console.log(`Now Mode '${payload}'`)
    mode0 = "off"
    spawn('irsend', ['SEND_ONCE', 'aircon', 'off'])
  }
  else if (payload === '17.0') {
    if (mode0 === 'heat') {
       console.log(`17.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_17'])
       }
    else if (mode0 === 'cool') {
       console.log(`17.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_17'])
       }
    else if (mode0 === 'auto') {
       console.log(`17.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_17'])   
       }
  }
  else if (payload === '18.0') {
    if (mode0 === 'heat') {
       console.log(`18.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_18'])
       }
    else if (mode0 === 'cool') {
       console.log(`18.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_18'])
       }
    else if (mode0 === 'auto') {    
       console.log(`18.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_18'])   
       }
  }
  else if (payload === '19.0') {
    if (mode0 === 'heat') {
       console.log(`19.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_19'])
       }
    else if (mode0 === 'cool') {
       console.log(`19.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_19'])
       }
    else if (mode0 === 'auto') {    
       console.log(`19.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_19'])   
       }
  }
  else if (payload === '20.0') {
    if (mode0 === 'heat') {
       console.log(`20.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_20'])
       }
    else if (mode0 === 'cool') {
       console.log(`20.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_20'])
       }
    else if (mode0 === 'auto') {    
       console.log(`20.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_20'])   
       }
  }
  else if (payload === '21.0') {
    if (mode0 === 'heat') {
       console.log(`21.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_21'])
       }
    else if (mode0 === 'cool') {
       console.log(`21.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_21'])
       }
    else if (mode0 === 'auto') {    
       console.log(`21.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_21'])
       }
  }
  else if (payload === '22.0') {
    if (mode0 === 'heat') {
       console.log(`22.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_22'])
       }
    else if (mode0 === 'cool') {
       console.log(`22.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_22'])
       }
    else if (mode0 === 'auto') {    
       console.log(`22.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_22'])
       }
  }
  else if (payload === '23.0') {
    if (mode0 === 'heat') {
       console.log(`23.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_23'])
       }
    else if (mode0 === 'cool') {
       console.log(`23.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_23'])
       }
    else if (mode0 === 'auto') {    
       console.log(`23.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_23'])   
       }
  }
  else if (payload === '24.0') {
    if (mode0 === 'heat') {
       console.log(`24.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_24'])
       }
    else if (mode0 === 'cool') {
       console.log(`24.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_24'])
       }
    else if (mode0 === 'auto') {    
       console.log(`24.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_24'])   
       }
  }
  else if (payload === '25.0') {
    if (mode0 === 'heat') {
       console.log(`25.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_25'])
       }
    else if (mode0 === 'cool') {
       console.log(`25.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_25'])
       }
    else if (mode0 === 'auto') {    
       console.log(`25.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_25'])   
       }
  }
  else if (payload === '26.0') {
    if (mode0 === 'heat') {
       console.log(`26.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_26'])
       }
    else if (mode0 === 'cool') {
       console.log(`26.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_26'])
       }
    else if (mode0 === 'auto') {    
       console.log(`26.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_26'])   
       }
  }
  else if (payload === '27.0') {
    if (mode0 === 'heat') {
       console.log(`27.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_27'])
       }
    else if (mode0 === 'cool') {
       console.log(`27.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_27'])
       }
    else if (mode0 === 'auto') {    
       console.log(`27.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_27'])   
       }
  }
  else if (payload === '28.0') {
    if (mode0 === 'heat') {
       console.log(`28.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_28'])
       }
    else if (mode0 === 'cool') {
       console.log(`28.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_28'])
       }
    else if (mode0 === 'auto') {    
       console.log(`28.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_28'])   
       }
  }
  else if (payload === '29.0') {
    if (mode0 === 'heat') {
       console.log(`29.0heat`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'heat_29'])
       }
    else if (mode0 === 'cool') {
       console.log(`29.0cool`)
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_29'])
       }
    else if (mode0 === 'auto') {    
       console.log(`29.0auto`)    
       spawn('irsend', ['SEND_ONCE', 'aircon', 'cool_29'])   
       }
  }
});

安装mqtt库: npm install mqtt --save
运行客户端: node aircon.js

修改HomeAssistant配置,这里需要对应自己设置的主题。

climate:
  - platform: mqtt
    name: Aircon
    modes:
      - off
      - cool
      - heat
      - auto
    mode_command_topic: "mitsubishi/ac/mode/set"
    temperature_command_topic: "mitsubishi/ac/temperature/set"
    

重启测试能否使用。

mqtt客户端作为服务运行,sudo vim /etc/systemd/system/node-mqtt@pi.service,需要修改路径。

[Unit]
Description=Mqtt client with Node

[Service]
ExecStart=/home/pi/smarthome/aircon.js
Restart=always
User=%i
Environment=PATH=/usr/bin:/usr/local/bin
Environment=NODE_ENV=production
WorkingDirectory=/home/pi/smarthome

[Install]
WantedBy=multi-user.target

sudo systemctl daemon-reload 刷新服务,sudo systemctl enable node-mqtt@pi.service开机启动。

说明

HomeAssistant中控制的时候有个问题,显示的false并不能关闭空调,而HomeKit中却可以关闭(siri关闭是调成自动模式),所以把选择Auto模式设置成发送关闭的红外码。

其他的Auto温度也都是直接用了制冷的,实在是懒得录了,这模式一般也没用吧。

这里是和遥控器一样通过红外发送来控制的,所以没有办法读到到底空调处于什么状态,也就是optimistic mode。这就会导致一个问题,设置了26度,然后打开制冷,其实执行的是'制冷28度'(这是我前面设置的),也就是说这个显示是不正确的,而模式打开后,再设置的温度就是准确的了。

HomeKit上面显示的室温好像没办法改变。

参考

https://bbs.hassbian.com/thread-1817-1-1.html
https://www.home-assistant.io/components/climate.mqtt/