小米空调伴侣接入

回复
blindlight
帖子: 98
注册时间: 周四 3月 30, 2017 00:03

小米空调伴侣接入

帖子 blindlight »

1. 安装python-miio库(代码中mirobo都可以改成miio)
2. 创建1个dummy硬件代表空调
3. 该硬件下创建5个设备,分别代表 模式(多段)、风速(多段)、扫风(开关)、温度设置(Setpoint)、功率(电量)
4. 各设备名字、idx、多段名称更新至脚本

PS 调试tips
1. 使用前先调试python脚本,命令如下:

代码: 全选

python3 acpartner.py <acp_ip> <power> <mode> <wind> <swing> <temperature>
各参数值参考在python脚本中的presents,范例如下:

代码: 全选

python3 acpartner.py 10.0.0.148 on cooler auto off 22
python3 acpartner.py 10.0.0.148 off heater 3 on 25
python3 acpartner.py 10.0.0.148 off
2. 如调试不成功,可能是你的空调代码特殊,比如我的大金,需要抓码分析后更新至presents后才可使用
抓码使用miio库,js环境,npm安装
https://github.com/aholstenson/miio (如果你安装了python-miio将会覆盖miio的cli命令)
使用wiresharks和安卓模拟器抓码,输出json文件后用miio命令解码 https://github.com/aholstenson/miio/blo ... rotocol.md
抓码结果显示
https://github.com/takatost/homebridge- ... r/issues/5
抓码后如果分析不来可以贴出来,有空会添加

3. python成功后再设置domoticz内lua或者其他events,上来就全部设置好不能用也不肯调试的概不帮忙

ac1.jpg
ac1.jpg (19.62 KiB) 查看 59831 次
ac2.jpg
ac2.jpg (28.88 KiB) 查看 59831 次
控制程序python控制程序更新已更新在其他楼层

代码: 全选

import mirobo
import urllib.request
import codecs
import sys

presets = {
    "default": {
        "description": "The Default Replacement of AC Partner",
        "defaultMain": "modelpomowiswttli",
        "VALUE": ["po", "mo", "wi", "sw", "tt", "li"],
        "po": {
            "type": "switch",
            "on": "1",
            "off": "0"
        },
        "mo": {
            "heater": "0",
            "cooler": "1",
            "auto": "2",
            "dehum": "3",
            "airSup": "4"
        },
        "wi": {
            "auto": "3",
            "1": "0",
            "2": "1",
            "3": "2"
        },
        "sw": {
            "on": "0",
            "off": "1"
        },
        "tt": "10",
        "li": "a0"
    },
    "0180111111": {
        "des": "media_1",
        "main": "0180111111pomowiswtt02"
    },
    "0180222221": {
        "des": "gree_1",
        "main": "0180222221pomowiswtt02"
    },
    "0100010727": {
        "des": "gree_2",
        "main": "0100010727pomowiswtt1100190t0t205002102000t6t0190t0t207002000000t4wt0",
        "off": "010001072701011101004000205002112000D04000207002000000A0",
        "EXTRA_VALUE": ["t0t", "t6t", "t4wt"],
        "t0t": "1",
        "t6t": "7",
        "t4wt": "4"
    },
    "0100004795": {
        "des": "gree_8",
        "main": "0100004795pomowiswtt0100090900005002"
    },
    "0180333331": {
        "des": "haier_1",
        "main": "0180333331pomowiswtt12"
    },
    "0180666661": {
        "des": "aux_1",
        "main": "0180666661pomowiswtt12"
    },
    "0180777771": {
        "des": "chigo_1",
        "main": "0180777771pomowiswtt12"
    },
    "0100010502": {
        "des": "daikin_1",
        "main": "0100010502pomowiswttA0000011DA2700C50000D711DA27004200005411DA27000039t0t00A0000006600000C18000cs",
        "off": "0100010502013112A0000011DA2700C50000D711DA27004200005411DA270000082000A0000006600000C1800081",
        "EXTRA_VALUE": ["t0t", "cs"],
        "t0t": "hex(int(cmd[4]) * 2)[2:]",
        "cs": "hex(int(cmd[4]) * 2 + 146)[2:]"
    }
}

#Domoticz服务器
domoticzserver = "127.0.0.1:8080"
#idx
# mode_idx = "154"
# wind_idx = "155"
# swing_idx = "156"
# set_temp_idx = "157"
acpower_idx = "158"

def domoticzrequest (url):
    response = urllib.request.urlopen(url)
    return response.read()

def get_current():
    power = info[1][2]
    mode = info[1][3]
    wind = info[1][4]
    swing = info[1][5]
    set_temp = info[1][7]
    acpower = info[2]
    return [power, mode, wind, swing, set_temp, acpower]

def get_code(cmd):
    if model not in presets:
        code = presets['default']['defaultMain']

        for i,k in enumerate(presets['default']['VALUE'][0:5]):
            locals()[k] = presets['default'][k][cmd[i]]
        li = presets['default']['li']

        code = code.replace('model', model)
        for k in presets['default']['VALUE'][0:5]:
            code = code.replace(k, locals()[k])
        code = code.replace('li', li)
    else:
        if cmd[0] == 'off':
            if model in presets:
                if 'off' in presets[model]:
                    code = presets[model]['off']
                else:
                    code = presets[model]['main']

        else:
            code = presets[model]['main']

            for i,k in enumerate(presets['default']['VALUE'][0:4]):
                locals()[k] = presets['default'][k][cmd[i]]
            tt = hex(int(cmd[4]))[2:]

            for k in presets['default']['VALUE'][0:5]:
                code = code.replace(k, locals()[k])

            if 'EXTRA_VALUE' in presets[model]:
                for k in presets[model]['EXTRA_VALUE']:
                    locals()[k] = eval(presets[model][k])

                for k in presets[model]['EXTRA_VALUE']:
                    code = code.replace(k, locals()[k])

    return code

ip = sys.argv[1]
Container = mirobo.Device.discover(ip)
token = str(codecs.encode(Container.checksum, 'hex'), 'utf-8')

AC_Parter = mirobo.Device(ip, token)
info = AC_Parter.send('get_model_and_state', [])
model = info[0][0:2] + info[0][8:16]
[current_power, current_mode, current_wind, current_swing, current_set_temp, acpower] = get_current()

if len(sys.argv) == 3:
    if str(sys.argv[2]) == 'status':
        domoticzrequest ("http://"+domoticzserver+"/json.htm?type=command&param=udevice&idx="+acpower_idx+"&nvalue=0&svalue="+acpower)
        print("Current Status:")
        print("Power: "+current_power)
        print("Mode: "+current_mode)
        print("Wind: "+current_wind)
        print("Swing: "+current_swing)
        print("Temperature: "+current_set_temp)
        print("AC Power: "+acpower+"W")
    elif str(sys.argv[2]) == 'off':
        power = "off"
        cmd = [power,current_mode, current_wind, current_swing, current_set_temp]
    else:
        print("Wrong Command!")
elif len(sys.argv) == 7:
    cmd = sys.argv[2:]

if 'cmd' in dir():
    code = get_code(cmd)

    if AC_Parter.send('send_cmd', [code]) == ['ok']:
        [current_power, current_mode, current_wind, current_swing, current_set_temp, acpower] = get_current()
        print("Success!")
    else:
        print("Wrong Command or Invaild Code!!")
lua脚本device类型

代码: 全选

commandArray = {}

ACPartner_IP = '10.0.0.148'

for key, value in pairs(devicechanged) do
  if (key == '卧室空调模式' or key == '卧室空调风速' or key == '卧室空调扫风' or key == '卧室空调温度') then

    mode = otherdevices['卧室空调模式']
    wind = otherdevices['卧室空调风速']
    swing = otherdevices['卧室空调扫风']
    temperature = math.floor(otherdevices_svalues['卧室空调温度'])

    if     (mode == 'Off')   then po = 'off' mo = 'auto'
    elseif (mode == '制冷')  then po = 'on' mo = 'cooler'
    elseif (mode == '制热')  then po = 'on' mo = 'heater'
    elseif (mode == '自动')  then po = 'on' mo = 'auto'
    elseif (mode == '送风')   then po = 'on' mo = 'dehum'
    elseif (mode == '除湿')   then po = 'on' mo = 'airSup'
    end

    if     (wind == '自动')  then wi = 'auto'
    elseif (wind == '1档') then wi = '1'
    elseif (wind == '2档') then wi = '2'
    elseif (wind == '3档') then wi = '3'
    end

    if     (swing == 'Off')  then sw = 'off'
    elseif (swing == 'On') then sw = 'on'
    end

    tt = tostring(temperature)

    cmd = ACPartner_IP.." "..po.." "..mo.." "..wi.." "..sw.." "..tt

    print(cmd)

    os.execute ('sudo python3 /home/pi/domoticz/scripts/python/acpartner.py '..cmd)

  end
end

return commandArray
lua脚本time类型(用于更新功率)

代码: 全选

commandArray = {}

ACPartner_IP = '10.0.0.148'
os.execute ('sudo python3 /home/pi/domoticz/scripts/python/acpartner.py '..ACPartner_IP..' status')

return commandArray
blindlight
帖子: 98
注册时间: 周四 3月 30, 2017 00:03

Re: 小米空调伴侣接入

帖子 blindlight »

沙发自己坐
几件事情,暂不会移植成插件形式,当然一个是还不太会,第二是插件系统不稳定
我一直推崇的还是脚本方式,即使是博联控制我仍然用脚本,理解透彻就不会被大神牵着鼻子走
主要参考项目两个:
1. homebridge的ac partner插件
https://github.com/LASER-Yi/homebridge-mi-acpartner
奇怪的是用他的我的大金可以不用抓码,但是未看到有特殊处理(js实在不熟)
2. hass的ac partner插件
https://github.com/mac-zhou/homeassistant-mi-acpartner
python学习,可以对比下我如何简化了code赋值,省去while——>if..elif..elif..冗长的判断加赋值过程,使用列表筛选
还有对于带有特殊代码需要算式的,使用eval函数将表达式直接作为字符串赋值给特殊码
blindlight
帖子: 98
注册时间: 周四 3月 30, 2017 00:03

Re: 小米空调伴侣接入

帖子 blindlight »

更新空调伴侣红外学习功能

代码: 全选

# -*- coding: utf-8 -*-

import mirobo
import urllib.request
import codecs
import sys
import json
import time

'''
ircode map
len(ir_code)+162 = int(ir_code[34:36], 16)*4
                ir_code[34:36] 0x4b 0x4c 0x4d 0x4e 0x4f
ir_code[22:24]
0x96                           0x07 0x08 0x09 0x0a 0x0b 
0x97                           0x08 0x09 0x0a 0x0b 0x0c 
0x98                           0x09 0x0a 0x0b 0x0c 0x0d 
0x99                           0x0a 0x0b 0x0c 0x0d 0x0e 
0x9a                           0x0b 0x0c 0x0d 0x0e 0x0f 
...                            ..... 
'''

path = sys.path[0]
ac_json = open(path+'/presets.json', 'r')
presets = json.load(ac_json)
ac_json.close()

try:
    ir_json = open(path+'/mi_ircode.json', 'r')
    mi_ircode = json.load(ir_json)
    ir_json.close()
except FileNotFoundError:
    print("No ircode file.")
    mi_ircode = {}


#Domoticz服务器
domoticzserver = "127.0.0.1:8080"

def domoticzrequest (url):
    response = urllib.request.urlopen(url)
    return response.read()

def get_current():
    power = info[1][2]
    mode = info[1][3]
    wind = info[1][4]
    swing = info[1][5]
    set_temp = info[1][7]
    acpower = info[2]
    return [power, mode, wind, swing, set_temp, acpower]

def get_code(cmd):
    if model not in presets:
        code = presets['default']['defaultMain']

        for i,k in enumerate(presets['default']['VALUE'][0:5]):
            locals()[k] = presets['default'][k][cmd[i]]
        li = presets['default']['li']

        code = code.replace('model', model)
        for k in presets['default']['VALUE'][0:5]:
            code = code.replace(k, locals()[k])
        code = code.replace('li', li)
    else:
        if cmd[0] == 'off':
            if model in presets:
                if 'off' in presets[model]:
                    code = presets[model]['off']
                else:
                    code = presets[model]['main']

        else:
            code = presets[model]['main']

            for i,k in enumerate(presets['default']['VALUE'][0:4]):
                locals()[k] = presets['default'][k][cmd[i]]
            tt = hex(int(cmd[4]))[2:]

            for k in presets['default']['VALUE'][0:5]:
                code = code.replace(k, locals()[k])

            if 'EXTRA_VALUE' in presets[model]:
                for k in presets[model]['EXTRA_VALUE']:
                    locals()[k] = eval(presets[model][k])

                for k in presets[model]['EXTRA_VALUE']:
                    code = code.replace(k, locals()[k])

    return code

def irlearn():
    for i,k in enumerate(mi_ircode):
        print(i+1, k)

    a_choice = input("Select accessory ID or creat a new accessory: ")
    try:
        num = int(a_choice)
        for i,k in enumerate(mi_ircode):
            if i+1 == num:
                accessory = k
                break
    except ValueError:
        accessory = a_choice

    print(accessory+"'s buttons")

    if accessory in mi_ircode:
        for i,button in enumerate(mi_ircode[accessory]):
            print(i+1, button)

    b_choice = input("Select button ID or creat a new button: ")
    try:
        num = int(b_choice)
        for i,k in enumerate(mi_ircode[accessory]):
            if i+1 == num:
                button = k
                break
    except ValueError:
        button = b_choice

    if AC_Partner.send('start_ir_learn', [30]) == ['ok']:
        print("Please press the button on your remote...")
        i = 0 
        while AC_Partner.send('get_ir_learn_result', []) == ['(null)'] and i<15:
            print("Still waiting...")
            time.sleep(1)
            i += 1
        if i == 15:
            print("Time Out")
        else:
            ir_code = AC_Partner.send('get_ir_learn_result', [])[0]
            if ir_code:
                code_list = list(ir_code)
                code_list[14:26] = '94701FFF96FF'
                check = hex(int(ir_code[34:36], 16)-68)[2:]
                if len(check)==1:
                    check = '0'+check
                code_list[33:36] = '7'+check
                ir_code = ''.join(code_list)
                print(ir_code)

        AC_Partner.send('end_ir_learn', [])
    
    if 'ir_code' not in dir():
        ir_code = ''
    
    return accessory, button, ir_code

def update_irjson(accessory,button,ir_code):
    if accessory in mi_ircode:
        mi_ircode[accessory][button] = ir_code
    else:
        mi_ircode[accessory] = {}
        mi_ircode[accessory][button] = ir_code
    irf = open(path+'/mi_ircode.json', 'w', encoding='utf-8')
    irf.write(json.dumps(mi_ircode))
    irf.close()
    print("ir code learned.")
            

ip = sys.argv[1]
Container = mirobo.Device.discover(ip)
token = str(codecs.encode(Container.checksum, 'hex'), 'utf-8')

AC_Partner = mirobo.Device(ip, token)
info = AC_Partner.send('get_model_and_state', [])
model = info[0][0:2] + info[0][8:16]
[current_power, current_mode, current_wind, current_swing, current_set_temp, acpower] = get_current()

if len(sys.argv) == 7:
    cmd = sys.argv[2:]
elif len(sys.argv) == 5:
    if str(sys.argv[2]) == 'ir':
        device = str(sys.argv[3])
        button  = str(sys.argv[4])
        try:
            ircode = mi_ircode[device][button]
            if AC_Partner.send('send_ir_code', [ircode]) == ['ok']:
                print("IR Sent!")
        except:
            print("No Such Device or Button!!")
elif len(sys.argv) == 4:
    if str(sys.argv[2]) == 'status':
        acpower_idx = str(sys.argv[3])
        domoticzrequest ("http://"+domoticzserver+"/json.htm?type=command&param=udevice&idx="+acpower_idx+"&nvalue=0&svalue="+acpower)
        print("Current Status:")
        print("Power: "+current_power)
        print("Mode: "+current_mode)
        print("Wind: "+current_wind)
        print("Swing: "+current_swing)
        print("Temperature: "+current_set_temp)
        print("AC Power: "+acpower+"W")
    if str(sys.argv[2]) == 'irsend':
        ircode = str(sys.argv[3])
        if AC_Partner.send('send_ir_code', [ircode]) == ['ok']:
            print("IR Sent!")
elif len(sys.argv) == 3:
    if str(sys.argv[2]) == 'off':
        power = "off"
        cmd = [power,current_mode, current_wind, current_swing, current_set_temp]
    elif str(sys.argv[2]) == 'irlearn':
        accessory, button, ir_code = irlearn()
        if ir_code != '':
            update_irjson(accessory, button, ir_code)
    else:
        print("Wrong Command!")

if 'cmd' in dir():
    code = get_code(cmd)

    if AC_Partner.send('send_cmd', [code]) == ['ok']:
        [current_power, current_mode, current_wind, current_swing, current_set_temp, acpower] = get_current()
        print("Success!")
    else:
        print("Wrong Command or Invaild Code!!")
将presets.json文件分出

代码: 全选

{
    "default": {
        "description": "The Default Replacement of AC Partner",
        "defaultMain": "modelpomowiswttli",
        "VALUE": [
            "po",
            "mo",
            "wi",
            "sw",
            "tt",
            "li"
        ],
        "po": {
            "type": "switch",
            "on": "1",
            "off": "0"
        },
        "mo": {
            "heater": "0",
            "cooler": "1",
            "auto": "2",
            "dehum": "3",
            "airSup": "4"
        },
        "wi": {
            "auto": "3",
            "1": "0",
            "2": "1",
            "3": "2"
        },
        "sw": {
            "on": "0",
            "off": "1"
        },
        "tt": "10",
        "li": "a0"
    },
    "0180111111": {
        "des": "media_1",
        "main": "0180111111pomowiswtt02"
    },
    "0180222221": {
        "des": "gree_1",
        "main": "0180222221pomowiswtt02"
    },
    "0100010727": {
        "des": "gree_2",
        "main": "0100010727pomowiswtt1100190t0t205002102000t6t0190t0t207002000000t4wt0",
        "off": "010001072701011101004000205002112000D04000207002000000A0",
        "EXTRA_VALUE": [
            "t0t",
            "t6t",
            "t4wt"
        ],
        "t0t": "1",
        "t6t": "7",
        "t4wt": "4"
    },
    "0100004795": {
        "des": "gree_8",
        "main": "0100004795pomowiswtt0100090900005002"
    },
    "0180333331": {
        "des": "haier_1",
        "main": "0180333331pomowiswtt12"
    },
    "0180666661": {
        "des": "aux_1",
        "main": "0180666661pomowiswtt12"
    },
    "0180777771": {
        "des": "chigo_1",
        "main": "0180777771pomowiswtt12"
    },
    "0100010502": {
        "des": "daikin_1",
        "main": "0100010502pomowiswttA0000011DA2700C50000D711DA27004200005411DA27000039t0t00A0000006600000C18000cs",
        "off": "0100010502013112A0000011DA2700C50000D711DA27004200005411DA270000082000A0000006600000C1800081",
        "EXTRA_VALUE": [
            "t0t",
            "cs"
        ],
        "t0t": "hex(int(cmd[4]) * 2)[2:]",
        "cs": "hex(int(cmd[4]) * 2 + 146)[2:]"
    }
}
红外用法:
学习

代码: 全选

python3 acpartner.py <ip> irlearn
如有设备会列出,选择设备和按钮,如需新建请键入其他字符(不能有空格,不能纯数字

使用

代码: 全选

python3 acpartner.py <ip> ir <accessory> <button>
调试

代码: 全选

python3 acpartner.py <ip> irsend <ir_code>
PS: 红外码调理不全面,如有问题请回复
PS2: 全面抛弃非射频博联
k111486
帖子: 3
注册时间: 周三 9月 06, 2017 12:54

Re: 小米空调伴侣接入

帖子 k111486 »

写的太快了,理解不了啊
blindlight
帖子: 98
注册时间: 周四 3月 30, 2017 00:03

Re: 小米空调伴侣接入

帖子 blindlight »

k111486 写了: 周二 10月 17, 2017 12:05 写的太快了,理解不了啊
理解不了群里问
k111486
帖子: 3
注册时间: 周三 9月 06, 2017 12:54

Re: 小米空调伴侣接入

帖子 k111486 »

想啊,估计看教程不仔细,没看到群地址
blindlight
帖子: 98
注册时间: 周四 3月 30, 2017 00:03

Re: 小米空调伴侣接入

帖子 blindlight »

企鹅群
330890635
arctg
帖子: 6
注册时间: 周日 9月 10, 2017 15:21

Re: 小米空调伴侣接入

帖子 arctg »

miio库是否没有读出耗电量(千瓦时)的数字,只是读到了功率(瓦)?
但是米家app可以显示出耗电量统计
回复