Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New device: Moes IR Thermostat (AC Controller) #1755

Closed
wants to merge 3 commits into from

Conversation

fabianoarruda
Copy link

@fabianoarruda fabianoarruda commented Mar 17, 2024

This is my attempt to add compatibility for Moes IR Thermostat - It is a IR controller specific to Air conditioners with built in temperature and humidity sensors. There is also a variant wihtout the LCD screen, which I believe should work the same, but I only have the LCD Screen variant, so not able to confirm.

Details about the device: https://moeshouse.com/products/wifi-ir-thermostat-with-ac-remote-and-sensor?variant=40016596303953

Now, I was able to add the device successfully and it read all attributes correctly, and it is able to set the AC functions - It will update the Tuya device status correctly, example if you change target temperature to 20 C, it will get reflected on the device screen and even in the app... The only problem is, changing the properties via DPS locally will not trigger the IR commands, So it will not set the actual AC temperature.

I have figured out how to send the IR commands via tinytuya using the Cloud integration, but I don't know yet how to bind these commands to the climate device, so when you attempt to change the device on HA, it should trigger the IR command.

Here is an example of code that triggers the IR command correcty so The AC will get the command, and the Tuya device screen will also reflect the change (and it will propagate to HA):

import tinytuya
import json

tinytuya.set_debug()

c = tinytuya.Cloud()

device_id = '...' # IR Thermostat

# Get a listing of all programmed "remotes". This is also the id of the subdevice you may see listed
# on Tuya Cloud as an "Air conditioner", after you configure your AC model in the app.
# In this result you will also find the remote_index attribute which I believe is 
# the IR codeset for a specific brand.

# Example response:
# "result": [
#         {
#             "area_id": 0,
#             "brand_id": 182,
#             "brand_name": "Midea",
#             "category_id": 5,
#             "operator_id": 0,
#             "remote_id": "eb0dc7........", # this is the same as subdevice id on Tuya Cloud
#             "remote_index": 11272,
#             "remote_name": "Ar condicionado"
#         }

# res = c.cloudrequest( '/v2.0/infrareds/' + device_id + '/remotes' )

remote_id = '...'

# list of supported standard AC codes:
# https://developer.tuya.com/en/docs/cloud/infrared-air-conditioner-apis?id=Kb3oe9ehg02fn

# Send command using AC standard code. This is the method that works with my specific AC (Midea)
post_data = {
  "category_id": 5,
  "remote_index": 11272,
  "code": "temp",
  "value": 20
}
res = c.cloudrequest( '/v2.0/infrareds/' + device_id + '/air-conditioners/' + remote_id + '/command', post=post_data )

print( res )
print( json.dumps(res, indent=2) )

Any help to figure out how to bind these 2 things together is really appreciated.

@fabianoarruda fabianoarruda force-pushed the moes_ir_thermostat branch 4 times, most recently from e990718 to 666495d Compare March 17, 2024 20:07
@fabianoarruda fabianoarruda force-pushed the moes_ir_thermostat branch 2 times, most recently from 0b1130f to f1f3453 Compare March 17, 2024 20:15
@fabianoarruda
Copy link
Author

posting here the result from "Query Things Data Model" for reference.

{
  "result": {
    "model": {
      "modelId": "00000492z1",
      "services": [
        {
          "actions": [],
          "code": "",
          "description": "",
          "events": [],
          "name": "默认服务",
          "properties": [
            {
              "abilityId": 1,
              "accessMode": "rw",
              "code": "infared_switch",
              "description": "",
              "extensions": {
                "iconName": "icon-power",
                "attribute": "2"
              },
              "name": "红外开关",
              "typeSpec": {
                "type": "bool",
                "typeDefaultValue": false
              }
            },
            {
              "abilityId": 2,
              "accessMode": "ro",
              "code": "temp_current",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_c",
                "attribute": "2",
                "trigger": "direct"
              },
              "name": "当前温度",
              "typeSpec": {
                "max": 600,
                "min": 0,
                "scale": 1,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 0,
                "unit": ""
              }
            },
            {
              "abilityId": 3,
              "accessMode": "rw",
              "code": "target_temp",
              "description": "APP界面能接受的范围是16到30",
              "extensions": {
                "iconName": "icon-dp_c",
                "trigger": "direct"
              },
              "name": "目标温度",
              "typeSpec": {
                "max": 30,
                "min": 16,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 16,
                "unit": ""
              }
            },
            {
              "abilityId": 4,
              "accessMode": "rw",
              "code": "mode",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_mode"
              },
              "name": "工作模式",
              "typeSpec": {
                "range": [
                  "cold",
                  "warm",
                  "auto",
                  "air",
                  "dehumidify"
                ],
                "type": "enum",
                "typeDefaultValue": "cold"
              }
            },
            {
              "abilityId": 5,
              "accessMode": "rw",
              "code": "fan_level",
              "description": "",
              "extensions": {
                "iconName": "icon-FanSpeed"
              },
              "name": "风速",
              "typeSpec": {
                "range": [
                  "auto",
                  "low",
                  "middle",
                  "high"
                ],
                "type": "enum",
                "typeDefaultValue": "auto"
              }
            },
            {
              "abilityId": 6,
              "accessMode": "ro",
              "code": "fault",
              "description": "",
              "extensions": {
                "iconName": "icon-baojing"
              },
              "name": "故障告警",
              "typeSpec": {
                "label": [
                  "serious_fault",
                  "sensor_fault"
                ],
                "maxlen": 2,
                "type": "bitmap",
                "typeDefaultValue": 0
              }
            },
            {
              "abilityId": 7,
              "accessMode": "rw",
              "code": "filter_reset",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_loop"
              },
              "name": "滤网复位",
              "typeSpec": {
                "type": "bool",
                "typeDefaultValue": false
              }
            },
            {
              "abilityId": 8,
              "accessMode": "ro",
              "code": "filter_life",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_time2"
              },
              "name": "滤网寿命",
              "typeSpec": {
                "max": 720,
                "min": 0,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 0,
                "unit": "h"
              }
            },
            {
              "abilityId": 9,
              "accessMode": "rw",
              "code": "upper_temp",
              "description": "APP界面能操作控制的范围是16到30",
              "extensions": {
                "iconName": "icon-dp_temp"
              },
              "name": "设置温度上限",
              "typeSpec": {
                "max": 30,
                "min": 16,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 16,
                "unit": ""
              }
            },
            {
              "abilityId": 10,
              "accessMode": "rw",
              "code": "lower_temp",
              "description": "APP界面能操作控制的范围是16到30",
              "extensions": {
                "iconName": "icon-dp_temp"
              },
              "name": "设置温度下限",
              "typeSpec": {
                "max": 30,
                "min": 16,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 16,
                "unit": ""
              }
            },
            {
              "abilityId": 11,
              "accessMode": "rw",
              "code": "temp_unit_convert",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_mode"
              },
              "name": "温标切换",
              "typeSpec": {
                "range": [
                  "c",
                  "f"
                ],
                "type": "enum",
                "typeDefaultValue": "c"
              }
            },
            {
              "abilityId": 12,
              "accessMode": "ro",
              "code": "humidity_current",
              "description": "",
              "extensions": {
                "iconName": "icon-shidu",
                "attribute": "2",
                "trigger": "direct"
              },
              "name": "当前湿度",
              "typeSpec": {
                "max": 100,
                "min": 0,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 0,
                "unit": "%"
              }
            },
            {
              "abilityId": 15,
              "accessMode": "rw",
              "code": "work_type",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_mode"
              },
              "name": "工作类型",
              "typeSpec": {
                "range": [
                  "scene_1",
                  "scene_2",
                  "scene_3"
                ],
                "type": "enum",
                "typeDefaultValue": "scene_1"
              }
            },
            {
              "abilityId": 16,
              "accessMode": "rw",
              "code": "status",
              "description": "",
              "extensions": {
                "iconName": "icon-zhuangtai"
              },
              "name": " 码库状态",
              "typeSpec": {
                "range": [
                  "done",
                  "run",
                  "idle"
                ],
                "type": "enum",
                "typeDefaultValue": "done"
              }
            },
            {
              "abilityId": 18,
              "accessMode": "ro",
              "code": "runtime",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_time3"
              },
              "name": "运行时间",
              "typeSpec": {
                "max": 999999,
                "min": 0,
                "scale": 0,
                "step": 1,
                "type": "value",
                "typeDefaultValue": 0,
                "unit": "h"
              }
            },
            {
              "abilityId": 19,
              "accessMode": "rw",
              "code": "internet_disc_switch",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_mode"
              },
              "name": "断联开关",
              "typeSpec": {
                "type": "bool",
                "typeDefaultValue": false
              }
            },
            {
              "abilityId": 20,
              "accessMode": "rw",
              "code": "runtime_total_reset",
              "description": "",
              "extensions": {
                "iconName": "icon-dp_loop"
              },
              "name": "复位累计工作时间",
              "typeSpec": {
                "type": "bool",
                "typeDefaultValue": false
              }
            },
            {
              "abilityId": 201,
              "accessMode": "rw",
              "code": "ir_send",
              "description": "",
              "extensions": {
                "iconName": "icon-icon-test11",
                "attribute": "2048"
              },
              "name": "红外控制下发",
              "typeSpec": {
                "maxlen": 3072,
                "type": "string",
                "typeDefaultValue": ""
              }
            },
            {
              "abilityId": 202,
              "accessMode": "ro",
              "code": "ir_study_code",
              "description": "",
              "extensions": {
                "iconName": "icon-shangsheng",
                "attribute": "2048"
              },
              "name": "红外学习值上报",
              "typeSpec": {
                "maxlen": 128,
                "type": "raw"
              }
            }
          ]
        }
      ]
    }
  }
}

@make-all
Copy link
Owner

make-all commented Mar 18, 2024

I think this is a compatible/same device as ir_moes_heatpump.yaml. Some of the previously unknown dps you have gotten the documentation for, as well as others that did not show up. It seems strange that it does not send the IR commands automatically, but your docs also have the ir_send and ir_learn dps, so we can add a remote entity to the existing config. The previous report noted that the climate controller dps were only working with the sub device after setting up the remote model, but you appear to have listed the product id of the main IR device, this may affect the IR sending, as it is not tied to a model if you don't use the sub device. Probably some dps need to be set as optional if your device is not being detected as ir_moes_heatpump already.

make-all added a commit that referenced this pull request Mar 19, 2024
PR #1755 contains detailed info on a Moes IR Thermostat (AC Controller)
that appears compatible (possibly the very same device).
This allows some of the previously unknown dps to be identified, and
some to be exposed as secondary entities, including the raw remote
controller so the device can be used to control additional devices.
@make-all
Copy link
Owner

Modified ir_moes_remote with the additional detail above.

@make-all make-all closed this Mar 19, 2024
@fabianoarruda
Copy link
Author

It seems strange that it does not send the IR commands automatically, but your docs also have the ir_send and ir_learn dps, so we can add a remote entity to the existing config.

The device does have a dps for sending IR commands, id 201 (ir_send), but looking at the logs on Tuya cloud it seems to be encrypted:

  {"control":"send_ir","ver":"3","head":"010ece0000000000040014003e00ab00ca","delay":300,"devid":"50:8b:b9:56:40:65","v_devid":"eb0dc7e67bf3fa1c989jsh","key_num":1,"key1":{"key":"M0_T19_S2","data":"02$$0030B24D5FA030CF@%","data_type":0,"relearn":false}}

So, not so easy to deal as the API via cloud. For what I could learn on a quick research, sending ir command via DPS requires specific IR codes which can vary depending on the AC model, while the Cloud API allows sending standard commands like turning on/off, set temp to a specific value etc.

Regarding the secondary device, by using Query Things Data Model I get a different set of attributes, but when pooling that secondary device localy using tinytuya, it is just reporting the same DPS as the main device, also it doesn't seem to respond to any attempt to set DPS.

Here is the result of Query Things Data Model for the secondary device:

{
  "result": {
    "model": {
      "modelId": "000000f3vv",
      "services": [
        {
          "actions": [],
          "code": "",
          "description": "",
          "events": [],
          "name": "默认服务",
          "properties": [
            {
              "abilityId": 1,
              "accessMode": "wr",
              "code": "control",
              "description": "",
              "name": "控制命令",
              "typeSpec": {
                "type": "enum",
                "range": [
                  "send_ir",
                  "study",
                  "study_exit",
                  "study_key"
                ]
              }
            },
            {
              "abilityId": 2,
              "accessMode": "ro",
              "code": "study_code",
              "description": "",
              "name": "学习编码",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            },
            {
              "abilityId": 3,
              "accessMode": "rw",
              "code": "ir_code",
              "description": "",
              "name": "红外编码",
              "typeSpec": {
                "type": "string",
                "maxlen": 255
              }
            },
            {
              "abilityId": 4,
              "accessMode": "rw",
              "code": "key_code",
              "description": "",
              "name": "码库按键参数",
              "typeSpec": {
                "type": "string",
                "maxlen": 255
              }
            },
            {
              "abilityId": 5,
              "accessMode": "rw",
              "code": "key_code2",
              "description": "",
              "name": "码库按键参数2",
              "typeSpec": {
                "type": "string",
                "maxlen": 255
              }
            },
            {
              "abilityId": 6,
              "accessMode": "rw",
              "code": "key_code3",
              "description": "",
              "name": "码库按键参数3",
              "typeSpec": {
                "type": "string",
                "maxlen": 255
              }
            },
            {
              "abilityId": 7,
              "accessMode": "rw",
              "code": "key_study",
              "description": "",
              "name": "来自学习参数下发",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            },
            {
              "abilityId": 8,
              "accessMode": "rw",
              "code": "key_study2",
              "description": "",
              "name": "来自学习参数下发2",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            },
            {
              "abilityId": 9,
              "accessMode": "rw",
              "code": "key_study3",
              "description": "",
              "name": "来自学习参数下发3",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            },
            {
              "abilityId": 10,
              "accessMode": "wr",
              "code": "delay_time",
              "description": "",
              "name": "红外码发送延时",
              "typeSpec": {
                "type": "value",
                "max": 65535,
                "min": 0,
                "scale": 1,
                "step": 1,
                "unit": "ms"
              }
            },
            {
              "abilityId": 11,
              "accessMode": "wr",
              "code": "key_code4",
              "description": "",
              "name": "新按键参数",
              "typeSpec": {
                "type": "string",
                "maxlen": 255
              }
            },
            {
              "abilityId": 12,
              "accessMode": "wr",
              "code": "key_study4",
              "description": "",
              "name": "新学习参数",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            },
            {
              "abilityId": 13,
              "accessMode": "rw",
              "code": "type",
              "description": "",
              "name": "码库标识",
              "typeSpec": {
                "type": "value",
                "max": 255,
                "min": 0,
                "scale": 1,
                "step": 1,
                "unit": ""
              }
            },
            {
              "abilityId": 101,
              "accessMode": "wr",
              "code": "switch_power",
              "description": "",
              "name": "开关",
              "typeSpec": {
                "type": "bool"
              }
            },
            {
              "abilityId": 102,
              "accessMode": "rw",
              "code": "mode",
              "description": "",
              "name": "模式",
              "typeSpec": {
                "type": "enum",
                "range": [
                  "0",
                  "1",
                  "2",
                  "3",
                  "4"
                ]
              }
            },
            {
              "abilityId": 103,
              "accessMode": "wr",
              "code": "temperature",
              "description": "",
              "name": "温度",
              "typeSpec": {
                "type": "value",
                "max": 40,
                "min": 10,
                "scale": 0,
                "step": 1,
                "unit": ""
              }
            },
            {
              "abilityId": 104,
              "accessMode": "rw",
              "code": "fan",
              "description": "",
              "name": "风量",
              "typeSpec": {
                "type": "enum",
                "range": [
                  "0",
                  "1",
                  "2",
                  "3"
                ]
              }
            },
            {
              "abilityId": 105,
              "accessMode": "wr",
              "code": "swing",
              "description": "",
              "name": "摆风",
              "typeSpec": {
                "type": "bool"
              }
            },
            {
              "abilityId": 201,
              "accessMode": "rw",
              "code": "ir_send",
              "description": "",
              "name": "红外控制下发",
              "typeSpec": {
                "type": "string",
                "maxlen": 3072
              }
            },
            {
              "abilityId": 202,
              "accessMode": "rw",
              "code": "ir_study_code",
              "description": "",
              "name": "红外学习值上报",
              "typeSpec": {
                "type": "raw",
                "maxlen": 128
              }
            }
          ]
        }
      ]
    }
  }
}

@fabianoarruda
Copy link
Author

It seems strange that it does not send the IR commands automatically, but your docs also have the ir_send and ir_learn dps, so we can add a remote entity to the existing config

@make-all Yes, thats how the device works. Setting the dps directly (i.e. Temperature, fan mode etc) will not trigger a ir command. But you can use ir_send DPS to send a command and the related DPS in the device wil be updated automatically. After tinkering with it a bit more I foun a way to succesfuly send IR commands. I found a pattern by watching Tuya cloud > debug device and was able to test IR command using tinytuya, and it works.

we can add a remote entity to the existing config

How could it be done? I noticed there is a remote.py , So I guess any code to add support for these IR commands would go there?

Here is an example of code that works:

import tinytuya
import json

# Connect to Device
d = tinytuya.OutletDevice(
    dev_id='MAIN_DEVICE_ID',
    address='DEVICE_IP_ADDRESS',
    local_key='your_key_here',
    version=3.3)


# power on with mode cold at 22 C
command = {"control":"send_ir","ver":"3","head":"010ece0000000000040014003e00ab00ca","delay":300,"devid":"MAC_ADDRESS_OF_DEVICE","v_devid":"[[REPLACE_WITH_SUB_DEVICE_ID]]","key_num":1,"key1":{"key":"M0_T22_S0","data":"02$$0030B24DBF40708F@%","data_type":0,"relearn":'false'}}

# power on
# command = {"head":"010ece0000000000040014003e00ab00ca","key1":{"data":"02$$0030B24DBF40D02F@%","data_type":0,"key":"power_on"},"devid":"","key2":{"data":"02$$0030B24D9F6010EF@%","data_type":0,"key":"M0_T18_S1"},"ver":"3","delay":300,"control":"send_ir","v_devid":"[[REPLACE_WITH_SUB_DEVICE_ID]]","key_num":2}

# power off
# command = {"head":"010ece0000000000040014003e00ab00ca","key1":{"data":"02$$0030B24D7B84E01F@%","data_type":0,"key":"power_off"},"devid":"","ver":"3","delay":300,"control":"send_ir","v_devid":"[[REPLACE_WITH_SUB_DEVICE_ID]]","key_num":1}


# Sending the IR command:
payload = d.generate_payload(tinytuya.CONTROL, {"201": json.dumps(command)})
d.send(payload)

@make-all
Copy link
Owner

The config has been modified already to implement the ir.send and ir.learn services. If you first learn a command using the ir.learn service, you can then send it by name. Otherwise you can send a single base64 encoded ir string that is the head and data above combined.

@fabianoarruda
Copy link
Author

@make-all Thanks for clarifying! Are they supposed to show up as services in Home assistant? Do you mind explaining how I can use them?

@make-all
Copy link
Owner

They are services under the remote entity.
The best documentation for remotes in HA is for the popular Broadlink device: https://www.home-assistant.io/integrations/broadlink/#remote, this integration has followed that quite closely.

@phoenixmarines
Copy link

@fabianoarruda Did you manage to get your device working, I'm still real novice with Home Assistant and can't seem to figure out how to link the Card in the dashboard to the remote send command I don't suppose you could help?

@fabianoarruda
Copy link
Author

@phoenixmarines Unfortunately No. It doesn't work. The only thing I managed to do so far, was to create a custom python script on HA to send remote commands to the device using Tuya Cloud, via tinytuya. I expose commands as services in HA and can use them on automations. For example, I created a turn_off service, I use it with a human presence sensor to turn the AC off when no one is in the room.

But I haven't figured out yet how to integrate this with the Climate Card. So, this is how it works on tuya-local currently: The Climate card will display all the information correctly from the device, but interacting with the card will only cause the Tuya App and LCD display of the IR thermostat to be updated. No actual IR commands are sent to the AC.

So, in my understanding the integration should not try to update the DPs directly (ie. target temp, Fan speed, etc), but the other way around: It should send the IR command, then the device will update the DPs automatically.

Additionally, there is another related discussion here on tinytuya, in which I'm trying to figure out how to send the commands locally, instead of via Cloud.

@technotiger
Copy link

@fabianoarruda Thank you for sharing your research. Did you make any further progress? I have a similar device (a newer color display variant) and am willing to help.

@fabianoarruda
Copy link
Author

@technotiger Not yet, unfortunately. Do you mind sharing more details about your device?

@technotiger
Copy link

technotiger commented May 17, 2024

@fabianoarruda This is the device (just a random listing out of many):
https://www.alibaba.com/product-detail/Tuya-smart-wifi-ble-air-conditioner_1600505666009.html
Model: (Neo) NAS-RT01W6
This device does not integrate as good as moes. There is no proper integration made for it yet, so it has some extra issues. The power on function does not work when the button on the device is pressed (it works from the app). Power off works. Still, it is a similar device and has the exact same issues as moes device being discussed here.

There is a possible workaround for your problem. You can create a template climate device. It can combine the working bits and pieces that you have got working so far into an integrated climate device. I have created some climate devices using a combination of sensors and IR blasters and it works perfectly. Check it out.
https://github.com/jcwillox/hass-template-climate
The end result will be a climate device that reads state locally and controls the device via cloud.

@fabianoarruda
Copy link
Author

Thank you very much @technotiger. I think the climate template you mentioned is exactly what I needed. I'll try using it for now. But in the long run I'm still researching on how to run the commands locally.

@technotiger
Copy link

You are welcome @fabianoarruda.

I am also using template climate for now. I noticed that the device has good alexa integration with temperature and mode controls (any missing modes can be run as scenes by alexa by first creating tap-and-run automations in the smartlife app). I am using alexa text commands (via alexa media player integration) instead of python scripts. This could be a simpler approach since we are relying on cloud anyway. I looked into your post where you are trying to figure out the codes for local control. My device uses the same format as yours with the codes being differently encoded. I am trying to figure it out, but it seems that the code part is encrypted/obfuscated very differently across devices. In the meanwhile, wouldn't it work, at-least for a particular device, if we simply record the code for each command and then replay it locally? If so, we can later create a solution which uses cloud once to record all codes for a particular device and then uses it locally ever after.

@fabianoarruda
Copy link
Author

@technotiger Yeah, I thought about that too. The problem is, the way the Cloud API works it will always send 3 variables per command: Mode, temperature and fan speed. Example: key M0_T24_S3 - which means Mode: Cool(0), Temperature: 24C, Speed 3. For my AC model (Midea) it means 146 unique keys.

So, I need to send via Cloud, watch Tuya developer console and record local command for each of these keys. I don't see an easy way to automate all that :/

@fabianoarruda
Copy link
Author

@technotiger Do you mind sharing your configuration for template-climate? I suspect I'm doing something wrong, I couldn't make it work yet.

@technotiger
Copy link

@fabianoarruda It is a bit tricky to get it to work correctly. I have used local_tuya to pull the needed values from my device as sensors. You need to create the scripts to set the mode and temperature. The scripts would have to handle the cases when the changes are made from the touch buttons, cloud delays, and possible loops due to cloud and local changes. I am also using an input_text helper to store the last known mode using an automation which ignores the unavailable and unknown states (because the device apparently has a weak wifi module). I plan to do the same for temperature.

Here is the configuration for the template climate.

climate:
  - platform: climate_template
    name: Living Room HVAC
    unique_id: ebxxxxxxxxxx2
    modes:
      - "auto"
      - "off"
      - "cool"
      - "heat"
      - "dry"
      - "fan_only"
    min_temp: 16
    max_temp: 30

    # available based on controller Alexa device's availability.
    availability_template: "{{ (not is_state('media_player.living_room_echo_dot', 'unavailable')) }}"

    # get current temp
    current_temperature_template: "{{ ( states('sensor.living_room_air_purifier_temperature')|float + states('sensor.ir_current_temp')|float ) / 2 }}"

    hvac_mode_template: "{{ states('input_text.living_room_ir_hvac_last_known_mode') }}"

    # get current humidity
    current_humidity_template: "{{ ( states('sensor.living_room_air_purifier_humidity')|float + states('sensor.ir_relative_humidity')|float ) / 2 }}"

    target_temperature_template: "{{ states('number.ir_hvac_target_temperature') }}"

    # hvac action
    set_temperature:
      - service: script.set_living_room_ir_hvac_temperature
        data_template:
          temperature: "{{ temperature }}"
          
    set_hvac_mode:
      - service: script.set_living_room_ir_hvac_mode
        data_template:
          mode: "{{ hvac_mode }}"

@fabianoarruda
Copy link
Author

Thank you again @technotiger, this is very helpful. I made it work, but I ended up using this forked version since the original version had some bugs, and it was not being maintained. For me it was not showing the fan speed controller even after I configured the template correctly. With this forked version it's working pretty well.

You need to create the scripts to set the mode and temperature. The scripts would have to handle the cases when the changes are made from the touch buttons, cloud delays, and possible loops due to cloud and local changes.

Can you give mote details? I having some of these issues already 😅

Other than that, it works pretty well. Here is my current config by the way:

  climate:
    - platform: climate_template
      name: Ar Sala
      unique_id: ar_sala
      hvac_modes:
        - "auto"
        - "dry"
        - "off"
        - "cool"
        - "heat"
        - "fan_only"
      fan_modes:
        - "auto"
        - "low"
        - "medium"
        - "high"
      
      min_temp: 17
      max_temp: 30
      
      hvac_mode_template: "{{ states('climate.ar_condicionado_sala') }}"
      
      fan_mode_template: "{{ state_attr('climate.ar_condicionado_sala', 'fan_mode') }}"
      
      target_temperature_template: "{{ state_attr('climate.ar_condicionado_sala', 'temperature') }}"
  
      # get current temp.
      current_temperature_template: "{{ states('sensor.ar_condicionado_sala_temperature') }}"
  
      # get current humidity.
      current_humidity_template: "{{ states('sensor.ar_condicionado_sala_humidity') }}"
  
      set_hvac_mode:
        - service: midea_remote.send_keys
          data:
            mode: "{{ states('climate.ar_sala') }}"
            temp: "{{ state_attr('climate.ar_sala', 'temperature') }}"
            wind: "{{ state_attr('climate.ar_sala', 'fan_mode') }}"
            
      set_fan_mode:
        - service: midea_remote.send_keys
          data:
            mode: "{{ states('climate.ar_sala') }}"
            temp: "{{ state_attr('climate.ar_sala', 'temperature') }}"
            wind: "{{ state_attr('climate.ar_sala', 'fan_mode') }}"
            
      set_temperature:
        - service: midea_remote.send_keys
          data:
            mode: "{{ states('climate.ar_sala') }}"
            temp: "{{ state_attr('climate.ar_sala', 'temperature') }}"
            wind: "{{ state_attr('climate.ar_sala', 'fan_mode') }}"

@technotiger
Copy link

Can you give mote details? I having some of these issues already 😅

If state changes locally from the device, ignore it to prevent loop. Adding a few seconds delay during which the mode script cannot be invoked again also helps. Ignoring unavailable state helps.

I am thinking of a hybrid approach wherein the most frequently used settings(say 10) are recorded from cloud logs and then used locally. The rest can continue to go through the cloud. I have not yet used any python scripts in HA. Are you using AppDaemon or pyscript?

@fabianoarruda
Copy link
Author

@technotiger Thanks again.

If state changes locally from the device, ignore it to prevent loop. Adding a few seconds delay during which the mode script cannot be invoked again also helps. Ignoring unavailable state helps.

I was able to fix this by comparing the current values with the requested ones, if nothing changed, ignore the command. But I'm curious to see how you did yours, if you don't mind sharing your code.

I am thinking of a hybrid approach wherein the most frequently used settings(say 10) are recorded from cloud logs and then used locally. The rest can continue to go through the cloud.

Yes, I'm tinkering with this right now, that's an interesting idea.

I have not yet used any python scripts in HA. Are you using AppDaemon or pyscript?

I'm using pyscript. Here is my code:

import tinytuya
import json

FAN_MODES = {
        'low': 1,
        'medium': 2,
        'high': 3,
        'auto': 0
    }
    
HVAC_MODES = {
    'cool':     0,
    'heat':     1,
    'auto':     2,
    'fan_only': 3,
    'dry':      4
}

    
c = task.executor(tinytuya.Cloud, apiRegion="us",
    apiKey="<api_key>",
    apiSecret="<api_secret>")
    

device_id = '<tuya_device_id>'

remote_id = '<remote_id>' # This is usually the secondary device you'll see in Tuya Cloud, after you have configured your AC model


# This is used for automations    
@service('midea_remote.power_off')
def power_off():
    
    post_data = {
      "power": 0
      }
      
    task.executor(c.cloudrequest, '/v2.0/infrareds/' + device_id + '/air-conditioners/' + remote_id + '/scenes/command', post=post_data )

   
# This is the method I'm currently using with the climate template
@service('midea_remote.send_keys')
def send_keys(mode='cool', temp=23, wind='auto'):
    
    current_mode = climate.ar_condicionado_sala
    current_temp = climate.ar_condicionado_sala.temperature
    current_wind = climate.ar_condicionado_sala.fan_mode
    
    if not (mode == current_mode and temp == current_temp and wind == current_wind):
    
        post_data = {}
        
        if mode == 'off':
            post_data = { "power": 0 }
        
        else:
            post_data = {
                "power": 1, # This parameter is always required. If not sent, Tuya API will complain
                "mode": HVAC_MODES[mode],
                "temp": temp,
                "wind": FAN_MODES[wind]
            }
        
        task.executor(c.cloudrequest, '/v2.0/infrareds/' + device_id + '/air-conditioners/' + remote_id + '/scenes/command', post=post_data )

@phoenixmarines
Copy link

@technotiger Yeah, I thought about that too. The problem is, the way the Cloud API works it will always send 3 variables per command: Mode, temperature and fan speed. Example: key M0_T24_S3 - which means Mode: Cool(0), Temperature: 24C, Speed 3. For my AC model (Midea) it means 146 unique keys.

So, I need to send via Cloud, watch Tuya developer console and record local command for each of these keys. I don't see an easy way to automate all that :/

@fabianoarruda

Have you seen this script which appears to convert the codes from Broadcom into ones that would work with Tuya. It may be a quicker way to get the codes to use on your device. Unfortunately I'm not very good at scripting so haven't worked it out yet.

https://gist.github.com/svyatogor/7839d00303998a9fa37eb48494dd680f

@technotiger
Copy link

technotiger commented Jun 21, 2024

@technotiger Thanks again.

If state changes locally from the device, ignore it to prevent loop. Adding a few seconds delay during which the mode script cannot be invoked again also helps. Ignoring unavailable state helps.

I was able to fix this by comparing the current values with the requested ones, if nothing changed, ignore the command. But I'm curious to see how you did yours, if you don't mind sharing your code.

Here is the current version of my mode change script. The temperature script has similar checks.

EDIT: Removed the code. Don't do it, the result is unreliable. I highly recommend using local control as described in my other comment.

@uzlonewolf
Copy link

So, I need to send via Cloud, watch Tuya developer console and record local command for each of these keys. I don't see an easy way to automate all that :/

TinyTuya has a Cloud.getdevicelog() function that can pull the developer console logs for you. Though I suspect once you get a few keys you can just see which bits are changing and thus only need a few keys to figure the protocol out.

@uzlonewolf
Copy link

For my AC model (Midea) it means 146 unique keys

After spending like an hour reverse engineering the examples you posted, I got the bright idea of searching "midea remote protocol" and what do you know, it's already documented at https://github.com/sheinz/esp-midea-ir/blob/master/midea-ir.c#L30 🤣 Should be trivial to just build your own IR codes with that.

timlaing pushed a commit to timlaing/tuya-local that referenced this pull request Aug 8, 2024
PR make-all#1755 contains detailed info on a Moes IR Thermostat (AC Controller)
that appears compatible (possibly the very same device).
This allows some of the previously unknown dps to be identified, and
some to be exposed as secondary entities, including the raw remote
controller so the device can be used to control additional devices.
@technotiger
Copy link

After getting sufficiently irritated by the cloud method, I finally converted my setup to fully local using (1)tinytuya based python script to send the IR/control codes locally (thanks @fabianoarruda), (2)template-climate to create the climate device in HA, and (3)local tuya for reading the DPs from the device.
The control codes don't seem to have any pattern, so I had to hardcode all the codes. But the good news is, there are not that many codes to be fetched. I fetched all Heat and Cool modes temperature codes (30 total). I never adjust the wind setting (fan speed) so I kept it constant. Dry and Auto modes have only one possible code each. Fan-only mode also needs just one code (as temp setting, though possible to send via code, has no effect in fan-only mode). Adding power on and off, there are only 35 codes needed for a fully functional local setup. It would take about 15-20 mins to manually fetch the all these codes. This is the end of the chapter for me as I now have fast and reliable local control. I can share the IR/control codes for the LG VRF system if anyone needs them. Thanks everyone!

@uzlonewolf
Copy link

uzlonewolf commented Aug 26, 2024

The control codes don't seem to have any pattern

Do you mind posting some of them? If they're the same ones LG has used in the past they're pretty easy to decode.

@technotiger
Copy link

The control codes don't seem to have any pattern

Do you mind posting some of them? If they're the same ones LG has used in the past they're pretty easy to decode.

Sure. Here you go. Let me know if you figure it out.

M0_T16_S3 through M0_T30_S3

01*&#%$0003C0%$0007FC%$0006F8%$000100%$000280%#%$000100%#^
01*&#%$0003C0%$0007FC%$0005F0%$000280%$000280%#%#%$000100^
01*&#%$0003C0%$0007FC%$0005F0%#%$000100%$000280%#%#%#%#^
01*&#%$0003C0%$0007FC%$0004E0%$0003C0%$0006F8^
01*&#%$0003C0%$0007FC%$0004E0%$000100%$000100%$0005F0%#^
01*&#%$0003C0%$0007FC%$0004E0%#%$000280%$0004E0%$000100^
01*&#%$0003C0%$0007FC%$0004E0%#%#%$000100%$0004E0%#%#^
01*&#%$0003C0%$0007FC%$0003C0%$0004E0%$0003C0%$000280^
01*&#%$0003C0%$0007FC%$0003C0%$000280%$000100%$0003C0%$000100%#^
01*&#%$0003C0%$0007FC%$0003C0%$000100%$000280%$0003C0%#%$000100^
01*&#%$0003C0%$0007FC%$0003C0%$000100%#%$000100%$0003C0%#%#%#^
01*&#%$0003C0%$0007FC%$0003C0%#%$0003C0%$000280%$0003C0^
01*&#%$0003C0%$0007FC%$0003C0%#%$000100%$000100%$000280%$000280%#^
01*&#%$0003C0%$0007FC%$0003C0%#%#%$000280%$000280%$000100%$000100^
01*&#%$0003C0%$0007FC%$0003C0%#%#%#%$000100%$000280%$000100%#%#^

M1_T16_S3 through M1_T30_S3

01*&#%$0003C0%$0007FC%#%$0005F0%$000100%$0005F0%#^
01*&#%$0003C0%$0007FC%#%$0004E0%$000280%$0004E0%$000100^
01*&#%$0003C0%$0007FC%#%$0004E0%#%$000100%$0004E0%#%#^
01*&#%$0003C0%$0007FC%#%$0003C0%$0003C0%$0003C0%$000280^
01*&#%$0003C0%$0007FC%#%$0003C0%$000100%$000100%$0003C0%$000100%#^
01*&#%$0003C0%$0007FC%#%$0003C0%#%$000280%$0003C0%#%$000100^
01*&#%$0003C0%$0007FC%#%$0003C0%#%#%$000100%$0003C0%#%#%#^
01*&#%$0003C0%$0007FC%#%$000280%$0004E0%$000280%$0003C0^
01*&#%$0003C0%$0007FC%#%$000280%$000280%$000100%$000280%$000280%#^
01*&#%$0003C0%$0007FC%#%$000280%$000100%$000280%$000280%$000100%$000100^
01*&#%$0003C0%$0007FC%#%$000280%$000100%#%$000100%$000280%$000100%#%#^
01*&#%$0003C0%$0007FC%#%$000280%#%$0003C0%$000280%#%$000280^
01*&#%$0003C0%$0007FC%#%$000280%#%$000100%$000100%$000280%#%$000100%#^
01*&#%$0003C0%$0007FC%#%$000280%#%#%$000280%$000280%#%#%$000100^
01*&#%$0003C0%$0007FC%#%$000280%#%#%#%$000100%$000280%#%#%#%#^

@phoenixmarines
Copy link

@technotiger can you please help with more info on how you Setup the tinytuya scripts to work with the template-climate. I need to setup a site with 100 heat pumps. Most are Fujitsus and Daikins so hoping I can reuse the codes between units.

@uzlonewolf
Copy link

@technotiger That appears to be the standard 28-bit format. Sometimes the decoding can get mangled and result in that mess. If you can post the head part it would make cleaning it up a lot easier.

@technotiger
Copy link

@technotiger can you please help with more info on how you Setup the tinytuya scripts to work with the template-climate. I need to setup a site with 100 heat pumps. Most are Fujitsus and Daikins so hoping I can reuse the codes between units.

I used pyscipt which exposes the python functions to HA. You can find my template-climate code above, which has remained almost the same. Template climate calls HA scripts to set temp and mode, the scripts call the python functions.

@technotiger That appears to be the standard 28-bit format. Sometimes the decoding can get mangled and result in that mess. If you can post the head part it would make cleaning it up a lot easier.

head: 020ed800000000000700100014001500380026009a013c

@uzlonewolf
Copy link

uzlonewolf commented Aug 26, 2024

Well, I finished brute forcing it right before I noticed you posted the head. Oh well, lol. It is LG's standard 28-bit code and actually cleans up well if you run it through the encoder I added to tinytuya:

M0
  01*&#%$0003C0%$0007FC%$0006F8%$000100%$000280%#%$000100%#^ == 880814d == command 8 mode 0 temp 16 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880814D0@^
  01*&#%$0003C0%$0007FC%$0005F0%$000280%$000280%#%#%$000100^ == 880824e == command 8 mode 0 temp 17 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880824E0@^
  01*&#%$0003C0%$0007FC%$0005F0%#%$000100%$000280%#%#%#%#^ == 880834f == command 8 mode 0 temp 18 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880834F0@^
  01*&#%$0003C0%$0007FC%$0004E0%$0003C0%$0006F8^ == 8808440 == command 8 mode 0 temp 19 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88084400@^
  01*&#%$0003C0%$0007FC%$0004E0%$000100%$000100%$0005F0%#^ == 8808541 == command 8 mode 0 temp 20 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88085410@^
  01*&#%$0003C0%$0007FC%$0004E0%#%$000280%$0004E0%$000100^ == 8808642 == command 8 mode 0 temp 21 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88086420@^
  01*&#%$0003C0%$0007FC%$0004E0%#%#%$000100%$0004E0%#%#^ == 8808743 == command 8 mode 0 temp 22 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88087430@^
  01*&#%$0003C0%$0007FC%$0003C0%$0004E0%$0003C0%$000280^ == 8808844 == command 8 mode 0 temp 23 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88088440@^
  01*&#%$0003C0%$0007FC%$0003C0%$000280%$000100%$0003C0%$000100%#^ == 8808945 == command 8 mode 0 temp 24 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C88089450@^
  01*&#%$0003C0%$0007FC%$0003C0%$000100%$000280%$0003C0%#%$000100^ == 8808a46 == command 8 mode 0 temp 25 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808A460@^
  01*&#%$0003C0%$0007FC%$0003C0%$000100%#%$000100%$0003C0%#%#%#^ == 8808b47 == command 8 mode 0 temp 26 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808B470@^
  01*&#%$0003C0%$0007FC%$0003C0%#%$0003C0%$000280%$0003C0^ == 8808c48 == command 8 mode 0 temp 27 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808C480@^
  01*&#%$0003C0%$0007FC%$0003C0%#%$000100%$000100%$000280%$000280%#^ == 8808d49 == command 8 mode 0 temp 28 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808D490@^
  01*&#%$0003C0%$0007FC%$0003C0%#%#%$000280%$000280%$000100%$000100^ == 8808e4a == command 8 mode 0 temp 29 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808E4A0@^
  01*&#%$0003C0%$0007FC%$0003C0%#%#%#%$000100%$000280%$000100%#%#^ == 8808f4b == command 8 mode 0 temp 30 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C8808F4B0@^
M1
  01*&#%$0003C0%$0007FC%#%$0005F0%$000100%$0005F0%#^ == 880c141 == command 8 mode 4 temp 16 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C1410@^
  01*&#%$0003C0%$0007FC%#%$0004E0%$000280%$0004E0%$000100^ == 880c242 == command 8 mode 4 temp 17 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C2420@^
  01*&#%$0003C0%$0007FC%#%$0004E0%#%$000100%$0004E0%#%#^ == 880c343 == command 8 mode 4 temp 18 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C3430@^
  01*&#%$0003C0%$0007FC%#%$0003C0%$0003C0%$0003C0%$000280^ == 880c444 == command 8 mode 4 temp 19 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C4440@^
  01*&#%$0003C0%$0007FC%#%$0003C0%$000100%$000100%$0003C0%$000100%#^ == 880c545 == command 8 mode 4 temp 20 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C5450@^
  01*&#%$0003C0%$0007FC%#%$0003C0%#%$000280%$0003C0%#%$000100^ == 880c646 == command 8 mode 4 temp 21 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C6460@^
  01*&#%$0003C0%$0007FC%#%$0003C0%#%#%$000100%$0003C0%#%#%#^ == 880c747 == command 8 mode 4 temp 22 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C7470@^
  01*&#%$0003C0%$0007FC%#%$000280%$0004E0%$000280%$0003C0^ == 880c848 == command 8 mode 4 temp 23 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C8480@^
  01*&#%$0003C0%$0007FC%#%$000280%$000280%$000100%$000280%$000280%#^ == 880c949 == command 8 mode 4 temp 24 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880C9490@^
  01*&#%$0003C0%$0007FC%#%$000280%$000100%$000280%$000280%$000100%$000100^ == 880ca4a == command 8 mode 4 temp 25 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CA4A0@^
  01*&#%$0003C0%$0007FC%#%$000280%$000100%#%$000100%$000280%$000100%#%#^ == 880cb4b == command 8 mode 4 temp 26 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CB4B0@^
  01*&#%$0003C0%$0007FC%#%$000280%#%$0003C0%$000280%#%$000280^ == 880cc4c == command 8 mode 4 temp 27 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CC4C0@^
  01*&#%$0003C0%$0007FC%#%$000280%#%$000100%$000100%$000280%#%$000100%#^ == 880cd4d == command 8 mode 4 temp 28 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CD4D0@^
  01*&#%$0003C0%$0007FC%#%$000280%#%#%$000280%$000280%#%#%$000100^ == 880ce4e == command 8 mode 4 temp 29 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CE4E0@^
  01*&#%$0003C0%$0007FC%#%$000280%#%#%#%$000100%$000280%#%#%#%#^ == 880cf4f == command 8 mode 4 temp 30 fan 4 [crc good!]
    cleaned up head: 020ED8000000000005001500100038009A0026 key: 01$%001C880CF4F0@^

As you can see the key is now just 01$%001C<hexval>0@^. The protocol timings look really close to NEC, so cleaning up the timings to match gives us a head of 010ED800000000000500150040015600AB0474.

My decoder isn't pretty but it gets the job done:

from tinytuya.Contrib import IRRemoteControlDevice

head = '020ed800000000000700100014001500380026009a013c'
codelist = {
    'M0': (
        '01*&#%$0003C0%$0007FC%$0006F8%$000100%$000280%#%$000100%#^',
        '01*&#%$0003C0%$0007FC%$0005F0%$000280%$000280%#%#%$000100^',
        '01*&#%$0003C0%$0007FC%$0005F0%#%$000100%$000280%#%#%#%#^',
        '01*&#%$0003C0%$0007FC%$0004E0%$0003C0%$0006F8^',
        '01*&#%$0003C0%$0007FC%$0004E0%$000100%$000100%$0005F0%#^',
        '01*&#%$0003C0%$0007FC%$0004E0%#%$000280%$0004E0%$000100^',
        '01*&#%$0003C0%$0007FC%$0004E0%#%#%$000100%$0004E0%#%#^',
        '01*&#%$0003C0%$0007FC%$0003C0%$0004E0%$0003C0%$000280^',
        '01*&#%$0003C0%$0007FC%$0003C0%$000280%$000100%$0003C0%$000100%#^',
        '01*&#%$0003C0%$0007FC%$0003C0%$000100%$000280%$0003C0%#%$000100^',
        '01*&#%$0003C0%$0007FC%$0003C0%$000100%#%$000100%$0003C0%#%#%#^',
        '01*&#%$0003C0%$0007FC%$0003C0%#%$0003C0%$000280%$0003C0^',
        '01*&#%$0003C0%$0007FC%$0003C0%#%$000100%$000100%$000280%$000280%#^',
        '01*&#%$0003C0%$0007FC%$0003C0%#%#%$000280%$000280%$000100%$000100^',
        '01*&#%$0003C0%$0007FC%$0003C0%#%#%#%$000100%$000280%$000100%#%#^',
    ),
    'M1': (
        '01*&#%$0003C0%$0007FC%#%$0005F0%$000100%$0005F0%#^',
        '01*&#%$0003C0%$0007FC%#%$0004E0%$000280%$0004E0%$000100^',
        '01*&#%$0003C0%$0007FC%#%$0004E0%#%$000100%$0004E0%#%#^',
        '01*&#%$0003C0%$0007FC%#%$0003C0%$0003C0%$0003C0%$000280^',
        '01*&#%$0003C0%$0007FC%#%$0003C0%$000100%$000100%$0003C0%$000100%#^',
        '01*&#%$0003C0%$0007FC%#%$0003C0%#%$000280%$0003C0%#%$000100^',
        '01*&#%$0003C0%$0007FC%#%$0003C0%#%#%$000100%$0003C0%#%#%#^',
        '01*&#%$0003C0%$0007FC%#%$000280%$0004E0%$000280%$0003C0^',
        '01*&#%$0003C0%$0007FC%#%$000280%$000280%$000100%$000280%$000280%#^',
        '01*&#%$0003C0%$0007FC%#%$000280%$000100%$000280%$000280%$000100%$000100^',
        '01*&#%$0003C0%$0007FC%#%$000280%$000100%#%$000100%$000280%$000100%#%#^',
        '01*&#%$0003C0%$0007FC%#%$000280%#%$0003C0%$000280%#%$000280^',
        '01*&#%$0003C0%$0007FC%#%$000280%#%$000100%$000100%$000280%#%$000100%#^',
        '01*&#%$0003C0%$0007FC%#%$000280%#%#%$000280%$000280%#%#%$000100^',
        '01*&#%$0003C0%$0007FC%#%$000280%#%#%#%$000100%$000280%#%#%#%#^',
    )
}

for k in codelist:
    print(k)
    for c in codelist[k]:
        pulses = IRRemoteControlDevice.head_key_to_pulses( head, c )
        new_head_key = IRRemoteControlDevice.pulses_to_head_key( pulses, fudge=0.1, freq=38 )
        result = full_result = int( new_head_key[1][8:15], 16 )
        #print( ' %s -> %x' % (c, result) )
        crc = result & 0x0F
        result >>= 4
        fan = result & 0x0F
        result >>= 4
        temp = result & 0x0F
        result >>= 4
        cmd_mode = result & 0xFF
        mode = result & 0x07
        #result >>= 4
        cmd = result & 0xF8
        result >>= 8
        header1 = result & 0x0F
        result >>= 4
        header2 = result & 0x0F
        result >>= 4

        want_crc = (fan + temp + (cmd_mode & 0x0F) + (cmd_mode >> 4) + header1 + header2) & 0x0F
        crc_good = 'good' if want_crc == crc else 'bad'

        #print('  %s = %x' % (c, full_result) )
        print( '  %s == %x == command %d mode %d temp %d fan %d [crc %s!]' % (c, full_result, cmd, mode, temp+15, fan, crc_good) )
        new_head_key = IRRemoteControlDevice.pulses_to_head_key(pulses)
        print( '    cleaned up head:', new_head_key[0], 'key:', new_head_key[1] )

I found a few other codes posted at https://raw.githubusercontent.com/nokru/lg-ac-lirc/master/encoded_values.txt and some of the commands decoded at https://forum.arduino.cc/t/solved-lg-air-conditioning-codes/80846/7 .

@slnnz
Copy link

slnnz commented Aug 26, 2024

@technotiger

Can you please provide an example of the PyScript to control locally. When you say you had to hard code each string how does this work.

I'm guessing the HA Climate Template calls a different HA Script based on the options you choose then that HA Script is linked to the corresponding PyScript but I'm not really sure if you need a separate PyScript for every option or is their a simpler way to do this with Variables.

@technotiger
Copy link

technotiger commented Aug 27, 2024

@uzlonewolf
Thanks! It makes sense. I could extend my setup by adding fan speed control. Given that we know the head and that it is the LG2 28-bit code. Is there an easy way (faster than manually copy pasting the codes from the logs) to generate codes for arbitrary commands like M1_T20_S1, M0_T24_S2 etc. I could also try to add commands for swing modes and other functionality that the tuya device does not provide and hence cannot be copied from the logs.

@slnnz
The temp and modes variables can be passed by the climate_template to the HA script, if you write so in the yaml. The 'mode' HA script can get the mode. The 'temperature' HA script can get both mode and temperature. The variables can further be passed on to the python script. I have just one python function to set mode and temp.

The python function can take variables.

@service
def ir_climate_set(mode='heat', temp=25):

The HA script can pass the variables to python.

    - action: pyscript.ir_climate_set
      metadata: {}
      data:
        mode: "{{ mode }}"
        temp: "{{ temperature|int }}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

6 participants