Is flashing a Tuya Universal Remote Controller with Tasmota worth it?

I’m sorry if this is the millionth post about flashing Tasmota on a Tuya device but following up from my previous post, I want to make sure it’s right step forward for what I want to achieve in Home Assistant.

I have a couple of Okos Universal Remote Controller (w/ Built-in Temperature & Humidity Sensor) devices which, as expected, show up in Home Assistant as unsupported devices.

These are my use cases:

  1. I want to able to see my air conditioners, which are controlled via these devices, to show up in Home Assistant.
  2. I want to able to turn the air conditioners on or off and increase or decrease the temperature. I don’t care about any other functions.
  3. I want to see the current status of the air conditioners (whether they are on or off) and see the current temperature in a card (perhaps a thermostat card or a custom card). I want to see this information even if I use the air conditioner remote or the Smart Life app.

These are the solutions I’ve come across so far:

  1. I’ve seen a few posts here, like this, suggesting to flash Tasmota on these devices. I am not very comfortable with soldering (would maybe prefer something like tuya-convert for a soldering free solution) but wanted to know if flashing Tasmota would really serve the use cases I mentioned above. Also would I still be able to use the Smart Life app?
  2. Related to Tasmota flashing, I’ve even seen Tasmota-IRHVAC and LG-Climate-component-for-Home-Assistant projects on GitHub. These projects seem interesting with a hardware component to serve use case 3 as well but again I am not comfortable with hardware, hence would look at these as a last resort.
  3. A workaround suggested seems promising, but not serve use case 3. If I were to go with this approach, I would probably create 4 scenes. Switch on AC (which, if possible, I’d default to 20 degrees), Switch off AC, Increase AC temperature and Decrease AC temperature. Is there a way (maybe via Node-RED), that we could update a few custom states/sensors so that I can see the AC temperature in Home Assistant?. If yes, can anyone guide me? The only drawback here would be that using the air conditioner remote or the Smart Life app would not update anything in Home Assistant, unless I use some kind of IR receiver (mentioned in solution 2).

A common question I have with the above solutions is would the air conditioners show up in Home Assistant as entities so that they can be controlled like any other entity?

Any thoughts on the above would be much appreciated.

After much deliberation, I’ve decided to try the Tasmota-IRHVAC and LG-Climate-component-for-Home-Assistant projects on GitHub, until Tuya official supports the integration in Home Assistant.

In the meanwhile, I was tinkering with some crude workarounds based on solution 3, which I thought I’d share for the benefit of anyone who would like to enhance it further.

Note: Home Assistant and Node-RED are Docker containers on a Raspberry Pi 4.

To start with, I created 4 Tap-to-run scenes in the Smart Life app: Switch on AC (which, if possible, I’d default to 20 degrees), Switch off AC, Increase AC temperature, and Decrease AC temperature.

Next, I restarted Home Assistant to sync these scenes via the Tuya Integration.

With these scenes now in Home Assistant, I created a new flow in Node-RED which tracks the triggers of these scenes, creates a JSON object to store the state and temperature of the AC, and saves this to a file in Node-RED’s volume under a custom_sensors folder.

Node-RED flow:

[{"id":"b329f38a.b28f9","type":"tab","label":"Master Bedroom AC","disabled":false,"info":""},{"id":"5afab58201e0355a","type":"server-events","z":"b329f38a.b28f9","name":"","server":"461c4d2a.d4d3c4","version":1,"event_type":"call_service","exposeToHomeAssistant":false,"haConfig":[{"property":"name","value":""},{"property":"icon","value":""}],"waitForRunning":true,"outputProperties":[{"property":"payload","propertyType":"msg","value":"","valueType":"eventData"},{"property":"topic","propertyType":"msg","value":"$outputData(\"eventData\").event_type","valueType":"jsonata"}],"x":110,"y":280,"wires":[["73a69158148ba100"]]},{"id":"81427887493dcaae","type":"function","z":"b329f38a.b28f9","name":"Transform Payload (Master Bedroom AC Turn On)","func":"msg.payload = { \"master_bedroom_ac\": { \"state\": \"on\", \"temperature\": 20 } };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":200,"wires":[["f0d2632f67a083ad","78272c272af9bfa3"]]},{"id":"49e09c04a6203e89","type":"switch","z":"b329f38a.b28f9","name":"","property":"payload.event.service_data.entity_id[0]","propertyType":"msg","rules":[{"t":"eq","v":"scene.master_bedroom_ac_turn_on","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_turn_off","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_increase_temperature","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_decrease_temperature","vt":"str"},{"t":"else"}],"checkall":"false","repair":false,"outputs":5,"x":310,"y":280,"wires":[["81427887493dcaae"],["22fff276e889ea6f"],["d4b62abb623a0a57"],["d4b62abb623a0a57"],["023f4b3f191f06a0"]]},{"id":"73a69158148ba100","type":"json","z":"b329f38a.b28f9","name":"","property":"payload","action":"obj","pretty":false,"x":230,"y":200,"wires":[["49e09c04a6203e89","6208f784bbd6b1d0"]]},{"id":"f0d2632f67a083ad","type":"file","z":"b329f38a.b28f9","name":"","filename":"/data/custom_sensors/sensor.master_bedroom_ac.json","appendNewline":true,"createDir":true,"overwriteFile":"true","encoding":"none","x":1170,"y":220,"wires":[[]]},{"id":"22fff276e889ea6f","type":"function","z":"b329f38a.b28f9","name":"Transform Payload (Master Bedroom AC Turn Off)","func":"msg.payload = { \"master_bedroom_ac\": { \"state\": \"off\" } };\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":240,"wires":[["f0d2632f67a083ad","78272c272af9bfa3"]]},{"id":"78272c272af9bfa3","type":"debug","z":"b329f38a.b28f9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":950,"y":120,"wires":[]},{"id":"6208f784bbd6b1d0","type":"debug","z":"b329f38a.b28f9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":390,"y":140,"wires":[]},{"id":"023f4b3f191f06a0","type":"switch","z":"b329f38a.b28f9","name":"","property":"payload.event.service_data.entity_id","propertyType":"msg","rules":[{"t":"eq","v":"scene.master_bedroom_ac_turn_on","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_turn_off","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_increase_temperature","vt":"str"},{"t":"eq","v":"scene.master_bedroom_ac_decrease_temperature","vt":"str"}],"checkall":"false","repair":false,"outputs":4,"x":210,"y":400,"wires":[["81427887493dcaae"],["22fff276e889ea6f"],["d4b62abb623a0a57"],["d4b62abb623a0a57"]]},{"id":"d4b62abb623a0a57","type":"file in","z":"b329f38a.b28f9","name":"","filename":"/data/custom_sensors/sensor.master_bedroom_ac.json","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":530,"y":400,"wires":[["400f6165aa5de09c"]]},{"id":"febddc72d1271abe","type":"debug","z":"b329f38a.b28f9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":930,"y":380,"wires":[]},{"id":"400f6165aa5de09c","type":"json","z":"b329f38a.b28f9","name":"","property":"payload","action":"","pretty":false,"x":830,"y":440,"wires":[["febddc72d1271abe","aea1ca234ab9684c","1ea94d4fe766302e"]]},{"id":"aea1ca234ab9684c","type":"function","z":"b329f38a.b28f9","name":"Transform Payload (Master Bedroom Increase Temperature)","func":"var currentState = msg.payload.master_bedroom_ac.state;\nvar currentTemperature = msg.payload.master_bedroom_ac.temperature;\n\nif (currentState === 'on') {\n    if (currentTemperature < 25) {\n        currentTemperature = currentTemperature + 1;\n    }\n    msg.payload = { \"master_bedroom_ac\": { \"state\": \"on\", \"temperature\": currentTemperature } };\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":280,"wires":[["f0d2632f67a083ad"]]},{"id":"1ea94d4fe766302e","type":"function","z":"b329f38a.b28f9","name":"Transform Payload (Master Bedroom Decrease Temperature)","func":"var currentState = msg.payload.master_bedroom_ac.state;\nvar currentTemperature = msg.payload.master_bedroom_ac.temperature;\n\nif (currentState === 'on') {\n    if (currentTemperature > 18) {\n        currentTemperature = currentTemperature - 1;\n    }\n    msg.payload = { \"master_bedroom_ac\": { \"state\": \"on\", \"temperature\": currentTemperature } };\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":680,"y":320,"wires":[["f0d2632f67a083ad"]]},{"id":"461c4d2a.d4d3c4","type":"server","name":"Home Assistant","version":1,"legacy":false,"addon":false,"rejectUnauthorizedCerts":true,"ha_boolean":"y|yes|true|on|home|open","connectionDelay":true,"cacheJson":true}]

JSON sensor file:

{ "master_bedroom_ac" : { "state" : "on", "temperature" : 20 } }

Since both Home Assistant and Node-RED are in Docker, I needed a way to read the JSON file which was in Node-RED’s volume in Home Assistant. To accomplish this, I created a custom_sensors folder in Home Assistant’s volume. I then created a bash script that watches the custom_sensors folder in Node-RED’s volume for any changes and copies the changed files to Home Assistant’s volume.

#!/usr/bin/env bash

 ## The source and target can contain spaces as
 ## long as they are quoted.
 source="/home/pi/Docker/Node-RED/data/custom_sensors"
 target="/home/pi/Docker/Home Assistant/config/custom_sensors"

 while true; do

   ## Watch for new files, the grep will return true if a file has
   ## been copied, modified or created.
   inotifywatch -e modify -e create -e moved_to -t 1 "$source" 2>/dev/null |
      grep total &&

   ## The -u option to cp causes it to only copy files
   ## that are newer in $source than in $target. Any files
   ## not present in $target will be copied.
   sudo cp -vu "$source"/* "$target"/
   # rsync -rc "$source/" "$target/" --delete
 done

I added this script to Raspberry Pi’s rc.local file so that it would run at every boot.

With the JSON file now appearing in Home Assistant’s volume, I proceeded to create File sensors in Home Assistant.

homeassistant:
  whitelist_external_dirs:
    - /config
    - /config/custom_sensors
  allowlist_external_dirs:
    - /config/custom_sensors
    - /config

sensor:
  - platform: file
    name: Master Bedroom AC State
    file_path: /config/custom_sensors/sensor.master_bedroom_ac.json
    value_template: '{{ value_json.master_bedroom_ac.state }}'
  - platform: file
    name: Master Bedroom AC Temperature
    file_path: /config/custom_sensors/sensor.master_bedroom_ac.json
    value_template: '{{ value_json.master_bedroom_ac.temperature }}'
    unit_of_measurement: "°C"

Finally, I created a custom card to visualize and control the AC.

image

image

image

type: custom:stack-in-card
mode: vertical
cards:
  - type: horizontal-stack
    cards:
      - type: custom:button-card
        show_name: false
        icon: mdi:thermometer-chevron-up
        size: 50px
        styles:
          icon:
            - color: |
                [[[
                  if (states['sensor.master_bedroom_ac_state'].state === 'on')
                    return "green";
                  if (states['sensor.master_bedroom_ac_state'].state === 'off')
                    return "#44739e"
                  return "#bdbdbd";
                ]]]
            - margin: 40px 0 0 0
        tap_action:
          action: call-service
          service: scene.turn_on
          service_data:
            entity_id: scene.master_bedroom_ac_increase_temperature
      - type: custom:button-card
        show_name: true
        name: '[[[ return states[''sensor.master_bedroom_ac_temperature''].state ]]]'
        show_icon: true
        icon: mdi:power-standby
        size: 200px
        show_label: true
        label: Master Bedroom AC
        styles:
          name:
            - color: green
            - font-size: 100px
            - display: |
                [[[
                  if (states['sensor.master_bedroom_ac_state'].state === 'on') return `block`
                  return `none`
                ]]]
          icon:
            - color: '#44739e'
            - display: |
                [[[
                  if (states['sensor.master_bedroom_ac_state'].state === 'off') return `block`
                  return `none`
                ]]]
        tap_action:
          action: call-service
          service: scene.turn_on
          service_data:
            entity_id: |
              [[[
                if (states['sensor.master_bedroom_ac_state'].state === 'on') return `scene.master_bedroom_ac_turn_off`
                return `scene.master_bedroom_ac_turn_on`
              ]]]
      - type: custom:button-card
        show_name: false
        icon: mdi:thermometer-chevron-down
        size: 50px
        styles:
          icon:
            - color: |
                [[[
                  if (states['sensor.master_bedroom_ac_state'].state === 'on')
                    return "green";
                  if (states['sensor.master_bedroom_ac_state'].state === 'off')
                    return "#44739e"
                  return "#bdbdbd";
                ]]]
            - margin: 40px 0 0 0
        tap_action:
          action: call-service
          service: scene.turn_on
          service_data:
            entity_id: scene.master_bedroom_ac_decrease_temperature

There are still several issues with this approach:

  • This only works with all flows starting and ending with Home Assistant. If a remote is used, there is no way the JSON would get updated.
  • There is a delay in updating the sensor, which is anywhere from 1 - 8 seconds.
  • Based on your air conditioner vendor, the remotes configured via the Smart Life app can issue different commands which makes it more cumbersome to use this workaround. For example, I have an LG air conditioner configured in the Smart Life app. Creating a scene to increase or decrease temperature results in scenes with the specific temperature being increased or decreased. Hence I’ll have to create multiple scenes for the increase and decrease commands.

In conclusion, the above workaround is very clunky and I’m going to start working on the Tasmota-IRHVAC and LG-Climate-component-for-Home-Assistant projects on GitHub and will take me a couple of months of hard work but it may be worth it.