Holman WiFi Tap timers intergration?

Same thing here with my WX1 system.

Absolutely amazing fork that derek and now NeriedAU have done here. Was searching everywhere to get them to work.

Everything still works fine with this reversed, just an annoying aesthetic issue.

On a secondary note, anyone know how to add a “number” entity into homekit? For the life of me I can’t see how to add that one for my iOS devices, as the entities to choose do not include number. This would be to control the manual water time (1 min - 60 min).

Cheers

You can try using a helper. Go to settings then devices and services and click helper.

I’ve mapped a few more DPS and added some templates with icons to make this a bit more frontend friendly:

      "entities": [
        {
          "friendly_name": "Soil Temperature",
          "unit_of_measurement": "\u2103",
          "device_class": "temperature",
          "id": 101,
          "platform": "sensor"
        },
        {
          "friendly_name": "Soil Moisture",
          "unit_of_measurement": "%",
          "device_class": "humidity",
          "id": 102,
          "platform": "sensor"
        },
        {
          "friendly_name": "Last water flow",
          "unit_of_measurement": "L",
          "device_class": "water",
          "id": 103,
          "platform": "sensor"
        },
        {
          "friendly_name": "Battery Capacity",
          "id": 105,
          "platform": "sensor"
        },
        {
          "friendly_name": "Irrigation status",
          "id": 106,
          "platform": "sensor"
        },
        {
          "friendly_name": "Manual timer",
          "min_value": 0.0,
          "max_value": 60.0,
          "step_size": 1.0,
          "restore_on_reconnect": false,
          "is_passive_entity": false,
          "id": 107,
          "platform": "number"
        },
        {
          "friendly_name": "Manual Switch",
          "restore_on_reconnect": false,
          "is_passive_entity": false,
          "id": 108,
          "platform": "switch"
        },
        {
          "friendly_name": "Rain delay",
          "select_options": "0;24;48;72",
          "select_options_friendly": "0h;24h;48h;72h",
          "restore_on_reconnect": false,
          "is_passive_entity": false,
          "id": 113,
          "platform": "select"
        },
        {
          "friendly_name": "Soil sensor presence",
          "state_on": "True",
          "state_off": "False",
          "id": 115,
          "platform": "binary_sensor"
        },
        {
          "friendly_name": "Rain sensor presence",
          "state_on": "True",
          "state_off": "False",
          "id": 116,
          "platform": "binary_sensor"
        },
        {
          "friendly_name": "Soil sensor power OK",
          "state_on": "True",
          "state_off": "False",
          "id": 117,
          "platform": "binary_sensor"
        },
        {
          "friendly_name": "Units",
          "select_options": "1;2",
          "select_options_friendly": "L/\u00b0C;Gal/\u00b0F",
          "restore_on_reconnect": false,
          "is_passive_entity": false,
          "id": 119,
          "platform": "select"
        },
        {
          "friendly_name": "Postponed due to rain",
          "state_on": "True",
          "state_off": "False",
          "id": 125,
          "platform": "binary_sensor"
        }

and template code:

template:
  - sensor:
    - name: "WX1 Hedge Battery Status"
      state: >-
          {% set mapper = {
            '0':'Empty',
            '1':'Half',
            '2':'Full'
          } %}
          {% set state = states('sensor.battery_capacity') %}
          {{ mapper[state] if state in mapper else state }}
      icon: >-
          {% if is_state('sensor.battery_capacity', '0') %}
            mdi:battery-outline
          {% elif is_state('sensor.battery_capacity', '1') %}
            mdi:battery-50
          {% else %}
            mdi:battery
          {% endif %}
  - sensor:
    - name: "WX1 Hedge Irrigation Status"
      state: >-
          {% set mapper = {
            '0':'Not Watering',
            '1':'Manual Watering',
            '2':'Watering A/B/C',
            '3':'Rain Delay Active'
          } %}
          {% set state = states('sensor.irrigation_status') %}
          {{ mapper[state] if state in mapper else state }}
      icon: >-
          {% if is_state('sensor.irrigation_status', '0') %}
            mdi:water-off
          {% elif is_state('sensor.irrigation_status', '1') %}
            mdi:water-plus
          {% elif is_state('sensor.irrigation_status', '2') %}
            mdi:water-sync
          {% else %}
            mdi:weather-pouring
          {% endif %}

switch:
  - platform: template
    switches:
      wx1_hedge_manual_switch:
        friendly_name: "WX1 Hedge Manual Switch"
        availability_template: "{{ not is_state('switch.manual_switch', 'unavailable') }}"
        value_template: "{{ is_state('switch.manual_switch', 'off') }}"
        turn_on:
          service: switch.turn_off
          target:
            entity_id: switch.manual_switch
        turn_off:
          service: switch.turn_on
          target:
            entity_id: switch.manual_switch
        icon_template: >-
          {% if is_state('switch.manual_switch', 'off') %}
            mdi:water
          {% else %}
            mdi:water-off
          {% endif %}

Thanks for all the work everyone has done here.

And here’s the config of the code as I pulled them from Tuya directly:

Soil Temperature: 101
	value in C
Soil Moisture: 102
	value in %
Last Water Flow: 103
	value in Litres
Rain Sensor Status: 104
	?
Battery Level: 105
	0: Replace
	1: Medium
	2: Good
Tap Timer Status: 106
	0: Not Watering
	1: Watering Manually
	2: Watering Start A/B/C
	3: Rain Delay
Manual Watering Setting (Mins): 107
	value in mins (1-60)

Manual Watering: 108
	On/Off
Manual Watering Time Left: 109
	?
Start A: 110
	encoded value
Start B: 111
	encoded value
Start C: 112
	encoded value
Watering Delay: 113
	0/24H/48H/72H
24 Hour Time: 114
	On/Off
Moisture Sensor: 115
	on/off
Add Rain Sensor Flag: 116
	on/off
Moisture Sensor Battery: 117
	false: Replace
	true: Good
Rain Sensor (NA): 118
	?
App Temperature Format: 119
	℃/℉
Alarm Status: 120
	0 ?
Flow Count: 121
	encoded value
Temp Count: 122
	encoded value
Moisture Count: 123
	encoded value
Rain Sensor Count: 124
	encoded value
Auto Irrigation Status: 125
	On/Off
System Time: 126
	?
MCU Version: 127
	string
Next Watering: 128
	encoded value

For the WX2 this can be done as well, by inspecting the logs:

So this looks amazing, can I just add these to my config files and Local Tuya will see them? Or do these get added a differnt way? I have a WX1 and hub and I can see them in the Tuya IOT but the WX1 shows a pop up of “no permissions” when I try and debug, but I can still pull logs lol.

Also running Get Device Info in Tuya IOT I get this back for the Hub and the WX1 (Hub shows up twice for some reason)

Wifi Hub (Actual)
{
  "result": {
    "active_time": 1676454950,
    "category": "wg2",
    "category_name": "Gateway",
    "create_time": 1676454878,
    "gateway_id": "",
    "icon": "smart/icon/ay1556838860681oKmdO/1746f926ee7e6541d8188bf6ec32801f.png",
    "id": "hidden",
    "ip": "hidden",
    "lat": "-37.7790",
    "local_key": "local key",
    "lon": "145.5748",
    "model": "Emate gateway",
    "name": "Wi-Fi Hub ",
    "online": true,
    "owner_id": "30302443",
    "product_id": "v9vxvkwqa48ayhlr",
    "product_name": "Wi-Fi Hub ",
    "sub": false,
    "time_zone": "+11:00",
    "update_time": 1676454950,
    "uuid": "hidden"
  },
  "success": true,
  "t": 1676680040467,
  "tid": "hidden"
}

Wifi Hub (Zig?)
{
  "result": {
    "active_time": 1676517236,
    "category": "cz",
    "category_name": "Socket",
    "create_time": 1676517236,
    "gateway_id": "Shows same as Wifi Hub ID",
    "icon": "smart/icon/ay1556838860681oKmdO/a475d09a86f87279a5fb63d3e8bcba84.png",
    "id": "hidden",
    "ip": "",
    "lat": "-37.7790",
    "local_key": "Shows same as Wifi Hub Local Key",
    "lon": "145.5748",
    "model": "EmateSocket",
    "name": "Backyard wifi hub",
    "node_id": "F0B",
    "online": true,
    "owner_id": "30302443",
    "product_id": "hzsl8cxxtpd0x56b",
    "product_name": "Wi-Fi Socket",
    "sub": true,
    "time_zone": "+11:00",
    "update_time": 1676517279,
    "uuid": "hidden"
  },
  "success": true,
  "t": 1676680086607,
  "tid": "hidden"
}
WX1 Timer 
{
  "result": {
    "active_time": 1676517239,
    "category": "kg",
    "category_name": "Switch",
    "create_time": 1676517239,
    "gateway_id": "Shows same as Wifi Hub ID",
    "icon": "smart/icon/ay1556838860681oKmdO/ab17f6e4e677bba7d3868a7a57cfe5b7.png",
    "id": "hidden",
    "ip": "",
    "lat": "-37.7790",
    "local_key": "Shows same as Wifi Hub Local Key",
    "lon": "145.5748",
    "model": "EMateWTT",
    "name": "Pool Filler",
    "node_id": "F01",
    "online": true,
    "owner_id": "30302443",
    "product_id": "zrsgzc8jktsricjj",
    "product_name": "WX1 Tap Timer",
    "sub": true,
    "time_zone": "+11:00",
    "update_time": 1676517263,
    "uuid": "hidden"
  },
  "success": true,
  "t": 1676679976805,
  "tid": "hidden"
}
1 Like

I’m adding manually via LocalTuya. I’ve done some more improvements, so now my HASS has full insight into everything I can see on the app. Adding the tap timer with the following steps:

Add new device

WX1 Tap Timer
Host <Hub IP address>
local_key <local key>
protocol 3.3
device_id <CID> <-- do not use the device ID!!!
manual DPS 101,102,103,105,106,107,108,110,111,112,113,114,115,117,119,121,122,123,125,128

Then I add all the sensors, binary_sensor, etc. I’ve added a full list with all required values here, and ordered by entity type, so it made it a bit more straight-forward (I had more than one unit to add):

sensor
DPS 101
WX1 Soil Temperature
unit °C
class temperature

sensor
DPS 102
WX1 Soil Moisture
unit %
class humidity

sensor
id 103
WX1 Last Water Flow
unit L
class water

sensor
id 105
WX1 Battery Level

sensor
id 106
WX1 Tap Timer Status

sensor
id 110
WX1 Start A (encoded)

sensor
id 111
WX1 Start B (encoded)

sensor
id 112
WX1 Start C (encoded)

sensor
id 121
WX1 Flow Count (encoded)

sensor
id 122
WX1 Temperature Count (encoded)

sensor
id 123
WX1 Moisture Count (encoded)

sensor
id 128
WX1 Next Watering (encoded)
restore_on_reconnect true

number
id 107
WX1 Manual Watering Setting (mins)
min_value 1,
max_value 60
step_size 1

switch
id 108
WX1 Watering (inverse)

select
id 113
WX1 Watering Delay
select_options 0;24;48;72
select_options_friendly 0h;24h;48h;72h

select
id 114
WX1 24 Hour Time
select_options 12;24
select_options_friendly 12H;24H

select
id 119
WX1 Sensor Units
select_options 1;2
select_options_friendly L/°C;Gal/°F

binary_sensor
id 115
WX1 Soil Sensor Present

binary_sensor
id 116
WX1 Rain Sensor Present

binary_sensor
id 117
WX1 Soil Sensor Power OK

binary_sensor
id 125
WX1 Postponed Due To Rain

Note that I’ve replace the “WX1” part with the area I’m watering (e.g. Hedge, Lawn, etc.). This will then change the entity names, which is important in the next part. I’ve also added some template entities to interpret the sensor data and make a proper non-inversed switch. Also note, this configuration is with soil sensor attached. I’ve left out some entities in my templates for units that don’t have the sensor attached:

template:
  - sensor:
    - name: "WX1 Battery Status"
      state: >-
          {% set mapper = {
            '0':'Empty',
            '1':'Half',
            '2':'Full'
          } %}
          {% set state = states('sensor.wx1_battery_level') %}
          {{ mapper[state] if state in mapper else state }}
      icon: >-
          {% if is_state('sensor.wx1_battery_level', '0') %}
            mdi:battery-outline
          {% elif is_state('sensor.wx1_battery_level', '1') %}
            mdi:battery-50
          {% else %}
            mdi:battery
          {% endif %}
  - sensor:
    - name: "WX1 Irrigation Status"
      state: >-
          {% set mapper = {
            '0':'Not Watering',
            '1':'Manual Watering',
            '2':'Watering A/B/C',
            '3':'Rain Delay Active'
          } %}
          {% set state = states('sensor.wx1_tap_timer_status') %}
          {{ mapper[state] if state in mapper else state }}
      icon: >-
          {% if is_state('sensor.wx1_tap_timer_status', '0') %}
            mdi:water-off
          {% elif is_state('sensor.wx1_tap_timer_status', '1') %}
            mdi:water-plus
          {% elif is_state('sensor.wx1_tap_timer_status', '2') %}
            mdi:water-sync
          {% else %}
            mdi:weather-pouring
          {% endif %}
  - sensor:
    - name: "WX1 Next Watering"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(xitems=[]) %}
        {% set lx = states('sensor.wx1_next_watering_encoded') | list %}
        {% for item in lx %}
          {% if item != "=" %}
            {% set data.xitems = data.xitems + [ base[item] ] %}
          {% endif %}
        {% endfor %}
        {% set bytecount = ( lx | length / 4 * 3 - ( lx | length - data.xitems | reject('undefined') | list | length )) | int %}
        {% set dataw = namespace(witems=[]) %}
        {% for cnt in range(0,lx | length, 4) %}
          {% if cnt + 1 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt] | bitwise_and(63) * 4 + ( data.xitems[cnt + 1] | bitwise_and(48) / 16)) ] %}
          {% endif %}
          {% if cnt + 2 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt + 1] | bitwise_and(15) * 16 + data.xitems[cnt + 2] | bitwise_and(60) / 4) ] %}
          {% endif %}
          {% if cnt + 3 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt + 2] | bitwise_and(3) + data.xitems[cnt + 3]) ] %}
          {% endif %}
        {% endfor %}
        {{ "N/A" if dataw.witems[2] == 0 else "%02d" | format(dataw.witems[2]) ~ "/" ~ "%02d" | format(dataw.witems[1]) ~ "/20" ~ "%02d" | format(dataw.witems[0]) ~ " at " ~ "%01d" | format(dataw.witems[3]) ~ ":" ~ "%02d" | format(dataw.witems[4]) }}
  - sensor:
    - name: "WX1 Flow Count"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(xitems=[]) %}
        {% set lx = states('sensor.wx1_flow_count_encoded') | list %}
        {% for item in lx %}
          {% if item != "=" %}
            {% set data.xitems = data.xitems + [ base[item] ] %}
          {% endif %}
        {% endfor %}
        {% set bytecount = ( lx | length / 4 * 3 - ( lx | length - data.xitems | reject('undefined') | list | length )) | int %}
        {% set dataw = namespace(witems=[]) %}
        {% for cnt in range(0,lx | length, 4) %}
          {% if cnt + 1 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt] | bitwise_and(63) * 4 + ( data.xitems[cnt + 1] | bitwise_and(48) / 16)) ] %}
          {% endif %}
          {% if cnt + 2 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt + 1] | bitwise_and(15) * 16 + data.xitems[cnt + 2] | bitwise_and(60) / 4) ] %}
          {% endif %}
          {% if cnt + 3 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ (data.xitems[cnt + 2] | bitwise_and(3) + data.xitems[cnt + 3]) ] %}
          {% endif %}
        {% endfor %}
        {% set dataf = namespace(fitems=[]) %}
        {% for cnt in range(0,dataw.witems | length, 2) %}
          {% if cnt + 1 < dataw.witems | length %}
            {% set dataf.fitems = [ (dataw.witems[cnt] * 256 + dataw.witems[cnt + 1] ) | int ] + dataf.fitems %}
          {% endif %}
        {% endfor %}
        {{ "Unavailable" if states('sensor.wx1_flow_count_encoded') == "unavailable" else dataf.fitems[0] }}
      state_class: total
      device_class: water
      unit_of_measurement: L
  - sensor:
    - name: "WX1 Moisture Count"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(xitems=[]) %}
        {% set lx = states('sensor.wx1_moisture_count_encoded') | list | reject('equalto','/') | list %}
        {% for item in lx %}
          {% if item != "=" %}
            {% set data.xitems = data.xitems + [ base[item] ] %}
          {% endif %}
        {% endfor %}
        {% set bytecount = ( lx | length / 4 * 3 - ( lx | length - data.xitems | reject('undefined') | list | length )) | int %}
        {% set dataw = namespace(witems=[]) %}
        {% for cnt in range(0,lx | length, 4) %}
          {% if cnt + 1 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt] | bitwise_and(63) * 4 + ( data.xitems[cnt + 1] | bitwise_and(48) / 16))) | int ] %}
          {% endif %}
          {% if cnt + 2 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt + 1] | bitwise_and(15) * 16 + data.xitems[cnt + 2] | bitwise_and(60) / 4)) | int ] %}
          {% endif %}
          {% if cnt + 3 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt + 2] | bitwise_and(3) + data.xitems[cnt + 3])) | int ] %}
          {% endif %}
        {% endfor %}
        {{ "Unavailable" if states('sensor.wx1_moisture_count_encoded') == "unavailable" else dataw.witems[0] }}
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"
  - sensor:
    - name: "WX1 Temperature Count"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(xitems=[]) %}
        {% set lx = states('sensor.wx1_temperature_count_encoded') | list | reject('equalto','/') | list %}
        {% for item in lx %}
          {% if item != "=" %}
            {% set data.xitems = data.xitems + [ base[item] ] %}
          {% endif %}
        {% endfor %}
        {% set bytecount = ( lx | length / 4 * 3 - ( lx | length - data.xitems | reject('undefined') | list | length )) | int %}
        {% set dataw = namespace(witems=[]) %}
        {% for cnt in range(0,lx | length, 4) %}
          {% if cnt + 1 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt] | bitwise_and(63) * 4 + ( data.xitems[cnt + 1] | bitwise_and(48) / 16))) | int ] %}
          {% endif %}
          {% if cnt + 2 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt + 1] | bitwise_and(15) * 16 + data.xitems[cnt + 2] | bitwise_and(60) / 4)) | int ] %}
          {% endif %}
          {% if cnt + 3 < data.xitems | length %}
            {% set dataw.witems = dataw.witems + [ ((data.xitems[cnt + 2] | bitwise_and(3) + data.xitems[cnt + 3])) | int ] %}
          {% endif %}
        {% endfor %}
        {{ "Unavailable" if states('sensor.wx1_temperature_count_encoded') == "unavailable" else dataw.witems[0] }}
      state_class: measurement
      device_class: temperature
      unit_of_measurement: °C
  - sensor:
    - name: "WX1 Start A"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(items=[]) %}
        {% set l = states('sensor.wx1_start_a_encoded') | list %}
        {% for item in l %}
          {% set data.items = data.items + [ base[item] ] %}
        {% endfor %}
        {% set hs = data.items[1] | bitwise_and(15) * 16 + data.items[2] | bitwise_and(60) / 4 %}
        {% set ms = data.items[2] | bitwise_and(3) + data.items[3] %}
        {% set h = data.items[4] | bitwise_and(63) * 4 + data.items[5] | bitwise_and(48) / 16 %}
        {% set m = data.items[5] | bitwise_and(15) * 16 + data.items[6] | bitwise_and(60) / 4 %}
        {% set sun = "Sun" if data.items[6] | bitwise_and(2) > 0 else "" %}
        {% set mon = "Mon" if data.items[6] | bitwise_and(1) > 0 else "" %}
        {% set tue = "Tue" if data.items[7] | bitwise_and(32) > 0 else "" %}
        {% set wed = "Wed" if data.items[7] | bitwise_and(16) > 0 else "" %}
        {% set thu = "Thu" if data.items[7] | bitwise_and(8) > 0 else "" %}
        {% set fri = "Fri" if data.items[7] | bitwise_and(4) > 0 else "" %}
        {% set sat = "Sat" if data.items[7] | bitwise_and(2) > 0 else "" %}
        {% set enable = data.items[7] | bitwise_and(1) > 0 %}
        {% set d = [ sun , mon , tue , wed , thu , fri , sat ] | reject("equalto","") | join(',') %}
        {% set timing = "%02d" | format(hs) ~ ":" ~ "%02d" | format(ms) ~ " for " ~ "%02d" | format(h) ~ ":" ~  "%02d" | format(m) ~ "h" %}
        {{ "Unavailable" if states('sensor.wx1_start_a_encoded') == "unavailable" else "On"  + "(" + d + ") at " + timing if enable else "Off" }}
  - sensor:
    - name: "WX1 Start B"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(items=[]) %}
        {% set l = states('sensor.wx1_start_b_encoded') | list %}
        {% for item in l %}
          {% set data.items = data.items + [ base[item] ] %}
        {% endfor %}
        {% set hs = data.items[1] | bitwise_and(15) * 16 + data.items[2] | bitwise_and(60) / 4 %}
        {% set ms = data.items[2] | bitwise_and(3) + data.items[3] %}
        {% set h = data.items[4] | bitwise_and(63) * 4 + data.items[5] | bitwise_and(48) / 16 %}
        {% set m = data.items[5] | bitwise_and(15) * 16 + data.items[6] | bitwise_and(60) / 4 %}
        {% set sun = "Sun" if data.items[6] | bitwise_and(2) > 0 else "" %}
        {% set mon = "Mon" if data.items[6] | bitwise_and(1) > 0 else "" %}
        {% set tue = "Tue" if data.items[7] | bitwise_and(32) > 0 else "" %}
        {% set wed = "Wed" if data.items[7] | bitwise_and(16) > 0 else "" %}
        {% set thu = "Thu" if data.items[7] | bitwise_and(8) > 0 else "" %}
        {% set fri = "Fri" if data.items[7] | bitwise_and(4) > 0 else "" %}
        {% set sat = "Sat" if data.items[7] | bitwise_and(2) > 0 else "" %}
        {% set enable = data.items[7] | bitwise_and(1) > 0 %}
        {% set d = [ sun , mon , tue , wed , thu , fri , sat ] | reject("equalto","") | join(',') %}
        {% set timing = "%02d" | format(hs) ~ ":" ~ "%02d" | format(ms) ~ " for " ~ "%02d" | format(h) ~ ":" ~  "%02d" | format(m) ~ "h" %}
        {{ "Unavailable" if states('sensor.wx1_start_b_encoded') == "unavailable" else "On"  + "(" + d + ") at " + timing if enable else "Off" }}
  - sensor:
    - name: "WX1 Start C"
      state: >-
        {% set base = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4, 'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9, 'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14, 'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19, 'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25, 'a': 26, 'b': 27, 'c': 28, 'd': 29, 'e': 30, 'f': 31, 'g': 32, 'h': 33, 'i': 34, 'j': 35, 'k': 36, 'l': 37, 'm': 38, 'n': 39, 'o': 40, 'p': 41, 'q': 42, 'r': 43, 's': 44, 't': 45, 'u': 46, 'v': 47, 'w': 48, 'x': 49, 'y': 50, 'z': 51, '0': 52, '1': 53, '2': 54, '3': 55, '4': 56, '5': 57, '6': 58, '7': 59, '8': 60, '9': 61, '+': 62, '/': 63} %}
        {% set data = namespace(items=[]) %}
        {% set l = states('sensor.wx1_start_c_encoded') | list %}
        {% for item in l %}
          {% set data.items = data.items + [ base[item] ] %}
        {% endfor %}
        {% set hs = data.items[1] | bitwise_and(15) * 16 + data.items[2] | bitwise_and(60) / 4 %}
        {% set ms = data.items[2] | bitwise_and(3) + data.items[3] %}
        {% set h = data.items[4] | bitwise_and(63) * 4 + data.items[5] | bitwise_and(48) / 16 %}
        {% set m = data.items[5] | bitwise_and(15) * 16 + data.items[6] | bitwise_and(60) / 4 %}
        {% set sun = "Sun" if data.items[6] | bitwise_and(2) > 0 else "" %}
        {% set mon = "Mon" if data.items[6] | bitwise_and(1) > 0 else "" %}
        {% set tue = "Tue" if data.items[7] | bitwise_and(32) > 0 else "" %}
        {% set wed = "Wed" if data.items[7] | bitwise_and(16) > 0 else "" %}
        {% set thu = "Thu" if data.items[7] | bitwise_and(8) > 0 else "" %}
        {% set fri = "Fri" if data.items[7] | bitwise_and(4) > 0 else "" %}
        {% set sat = "Sat" if data.items[7] | bitwise_and(2) > 0 else "" %}
        {% set enable = data.items[7] | bitwise_and(1) > 0 %}
        {% set d = [ sun , mon , tue , wed , thu , fri , sat ] | reject("equalto","") | join(',') %}
        {% set timing = "%02d" | format(hs) ~ ":" ~ "%02d" | format(ms) ~ " for " ~ "%02d" | format(h) ~ ":" ~  "%02d" | format(m) ~ "h" %}
        {{ "Unavailable" if states('sensor.wx1_start_c_encoded') == "unavailable" else "On"  + "(" + d + ") at " + timing if enable else "Off" }}

I’ve actually broken this out into a separate file. And I’ve added a switch as well, as mentioned:

switch:
  - platform: template
    switches:
      wx1_manual_switch:
        friendly_name: "WX1 Manual Watering"
        availability_template: "{{ not is_state('switch.wx1_watering_inverse', 'unavailable') }}"
        value_template: "{{ is_state('switch.wx1_watering_inverse', 'off') }}"
        turn_on:
          service: switch.turn_off
          target:
            entity_id: switch.wx1_watering_inverse
        turn_off:
          service: switch.turn_on
          target:
            entity_id: switch.wx1_watering_inverse
        icon_template: >-
          {% if is_state('switch.wx1_watering_inverse', 'off') %}
            mdi:water
          {% else %}
            mdi:water-off
          {% endif %}

Looks like this:

Note: For the temperature and flow counts, I only show the last read, however the result can be graphed after I’ve set the sensor state class. Flow Count can also be graphed and shows only the yesterday’s value. Many of the more complex and manually added entities can take up to 12 hours to receive their value and may display Unavailable for a while. All values are read and accessible, but then the template needs to be altered.

1 Like

That second ‘hub’ is not a hub. It’s a socket. Basically a power switch. It comes with the hub.

Thanks for the reply, but I appear to be getting stuck with the CID part.
I am using tuya-cli and getting back this error:

And just to make sure it wasnt a vlan issue (all the IOT is on a differnt vlan) I ran the same command to one of the Tuya light switches:

As you can see the light switch gets back a DPS list, any of the Holman items come back with “DEVID not found”

What am I missing here?

Have you tried just with the tuya cli wizard? That’s all I used, then went straight to HA from there.

That got the correct CID now thanks. It turns out I did manage to find it last time (cant remember how) but the confusing part to me now, if when I restart HA all the sensors/switchs/etc show as univaliable until they change in the app. Is this what you see too? How do you get around this? I dont want to have to open the app eveytime I restart HA or we have a power outage or something.

You can see the logbook shows activity but after reboot all the items are now univalable.

It’s what I had as well. I’ve now installed the Var integration and made buffer entities (actually variables) that now store the last state while the dps are unavailable (once they change, they report something. I can post that solution tomorrow. I restart quite frequently as I do some development on another integration, so this was quite necessary to figure out.

Something is different for me though. I only see the “silent” dps go unavailable. The others report instantly. You may be able to solve that with an automation though, setting a value forth and back?

I’ve added a pull request to the original localtuya repo, as I don’t want to be stuck on a local branch forever: Adding support for local sub-devices (nodes) by funtastix · Pull Request #1305 · rospogrigio/localtuya · GitHub

2 Likes

For persistence, as mentioned above, I’ve installed the Variables integration and added a configuration to buffer the tap timer values while they may be unavailable:

  wx1_start_a_buffer:
    friendly_name: "WX1 Start A Buffer"
    initial_value: "AAAAAAAAAAAA"
    value_template: >
      {% if is_state('sensor.wx1_start_a_encoded','unavailable') or is_state('sensor.wx1_start_a_encoded','unknown') %}
        {{ states('var.wx1_start_a_buffer') }}
      {% else %}
        {{ states('sensor.wx1_start_a_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_start_a_encoded
  wx1_start_b_buffer:
    friendly_name: "WX1 Start B Buffer"
    initial_value: "AAAAAAAAAAAA"
    value_template: >
      {% if is_state('sensor.wx1_start_b_encoded','unavailable') or is_state('sensor.wx1_start_b_encoded','unknown') %}
        {{ states('var.wx1_start_b_buffer') }}
      {% else %}
        {{ states('sensor.wx1_start_b_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_start_b_encoded
  wx1_start_c_buffer:
    friendly_name: "WX1 Start C Buffer"
    initial_value: "AAAAAAAAAAAA"
    value_template: >
      {% if is_state('sensor.wx1_start_c_encoded','unavailable') or is_state('sensor.wx1_start_c_encoded','unknown') %}
        {{ states('var.wx1_start_c_buffer') }}
      {% else %}
        {{ states('sensor.wx1_start_c_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_start_c_encoded
  wx1_temperature_count_buffer:
    friendly_name: "WX1 Temperature Count Buffer"
    initial_value: 0
    value_template: >
      {% if is_state('sensor.wx1_temperature_count_encoded','unavailable') or is_state('sensor.wx1_temperature_count_encoded','unknown') %}
        {{ states('var.wx1_temperature_count_buffer') }}
      {% else %}
        {{ states('sensor.wx1_temperature_count_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_temperature_count_encoded
  wx1_moisture_count_buffer:
    friendly_name: "WX1 Moisture Count Buffer"
    initial_value: 0
    value_template: >
      {% if is_state('sensor.wx1_moisture_count_encoded','unavailable') or is_state('sensor.wx1_moisture_count_encoded','unknown') %}
        {{ states('var.wx1_moisture_count_buffer') }}
      {% else %}
        {{ states('sensor.wx1_moisture_count_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_moisture_count_encoded
  wx1_flow_count_buffer:
    friendly_name: "WX1 Flow Count Buffer"
    initial_value: 0
    value_template: >
      {% if is_state('sensor.wx1_flow_count_encoded','unavailable') or is_state('sensor.wx1_flow_count_encoded','unknown') %}
        {{ states('var.wx1_flow_count_buffer') }}
      {% else %}
        {{ states('sensor.wx1_flow_count_encoded') }}
      {% endif %}
    tracked_entity_id:
      - sensor.wx1_flow_count_encoded

in configuration.yaml include the line

var: !include var.yaml

and change the entities in your templates to the buffered ones above.

Thanks @funtastix & @NeriedAU and everyone else who’s contributed on this. It’s working well for me.

I did try a few of the no name ZigBee water controllers and while the ZigBee part worked fine, the actual water solenoids were rubbish.

The Holman units seem to be good quality.

@funtastix could you please post the relevant part of your /config/.storage/core.config_entries file?
I’d like to add in those extra DPS’s.

It’s all posted up at post 147 or 148. I am thinking of reorganising my setup a bit and just making one config file per unit. Today I’ve been able to make a template switch to turn on and off the timers, proving the concept that it can all be controlled from HA. I’m thinking of putting up a GitHub repo or contributing to the NeriedAU one to describe my full approach. I’m still working on getting the changes back into the main localtuya, but there’s a problem with multiple devices that I’m stuck at.

I’ve uploaded my full approach into a branch on my github here:

3 Likes

Hey folks - bit late on this one. I’ve been using the WX1 taps for a while (not yet integrated into HA). Was at Bunnings yesterday and saw the Bluetooth only version (BX1 and BX2). I was wondering if it would be possible to integrate those directly into HA if it’s close enough to my Raspberry Pi (I also have some of the ESP32 Bluetooth Relays as well).

Has anyone managed to do that?

1 Like

@deanpribetic so; you might face a few issues with discovery, as well as reverse engineering whatever bluetooth protocol is in use.
Not impossible, but with the localtuya/wifi versions you have at least the Tuya protocol for read values, push values and the mapping in this thread.
It’s a fair bit of work even with that however!

1 Like