Neptun Smart water leakage protection system integration

I have water leakage protection system called “Neptun Smart” with tuya controller. I haven’t been able to find info how to configure HA to use it so I decided to share my experience.

neptun-smart

Using “Tuya API Explorer - Query Things Data Model” I have been able to get code descriptions and using LocalTuya added a new device. Unfortunately I haven’t found the way to share device profile cause LocalTuya doesn’t use YAML configuration anymore.

Protocol version 3.3

I haven’t been able to insert table so share original json using code.

{
   "result":{
      "model":{
         "modelId":"000002fhoc",
         "services":[
            {
               "actions":[
                  
               ],
               "code":"",
               "description":"",
               "events":[
                  
               ],
               "name":"默认服务",
               "properties":[
                  {
                     "abilityId":101,
                     "accessMode":"rw",
                     "code":"floor_cleaning_flag",
                     "description":"Implemented as a switch in the settings window",
                     "extensions":{
                        "iconName":"icon-dp_water",
                        "attribute":"4"
                     },
                     "name":"Floor cleaning",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":108,
                     "accessMode":"rw",
                     "code":"add_new_sensors",
                     "description":"Flag of  starting procedure of add new sensors. Define as button in Sensor settings window.",
                     "name":"Add new sensors",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":109,
                     "accessMode":"rw",
                     "code":"status_crane_group_1",
                     "description":"Status of crane first group. Define as switch in main window of app",
                     "extensions":{
                        "iconName":"icon-icon-test10",
                        "attribute":"5"
                     },
                     "name":"Status crane group 1",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":110,
                     "accessMode":"rw",
                     "code":"status_crane_group_2",
                     "description":"Status of crane second group. Define as switch in main window of app",
                     "extensions":{
                        "iconName":"icon-icon-test10",
                        "attribute":"5"
                     },
                     "name":"Status crane group 2",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":111,
                     "accessMode":"rw",
                     "code":"group_eneble",
                     "description":"Enable separate on two group. Define as switch in config window",
                     "name":"Group enable",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":112,
                     "accessMode":"rw",
                     "code":"close_if_sensor_loss",
                     "description":"Enable of closing cranes on loss of RF sensors . Define as switch in config window.",
                     "name":"Close if sensor loss",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":113,
                     "accessMode":"rw",
                     "code":"child_protection",
                     "description":"If enabled then keyboard is blocked. Define in config window as switch.",
                     "extensions":{
                        "iconName":"icon-dp_lock",
                        "attribute":"4"
                     },
                     "name":"Child protection",
                     "typeSpec":{
                        "type":"bool"
                     }
                  },
                  {
                     "abilityId":114,
                     "accessMode":"rw",
                     "code":"line_settings",
                     "description":"Settings for four lines.\n{input_type_1, control_group_1 },\n{input_type_2, control_group_2 },\n{input_type_3, control_group_3 },\n{input_type_4, control_group_4 }\n\ninput_type - byte, ref. as (0-sensor/1-key)\ncontrol_group - byte, ref. as (1-control group 1; 2-control group 2; 3-control both group)\n",
                     "name":"Line settings",
                     "typeSpec":{
                        "type":"raw",
                        "maxlen":128
                     }
                  },
                  {
                     "abilityId":115,
                     "accessMode":"ro",
                     "code":"status_wire_line",
                     "description":"Status each from four wire lines.\n{status_line_1, status_line_2, status_line_3, status_line_4}\nEach status_line is а boolean. Use for notifications.",
                     "name":"Status wire line",
                     "typeSpec":{
                        "type":"value",
                        "max":255,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":116,
                     "accessMode":"rw",
                     "code":"relay_settings",
                     "description":"Configuring relay triggering by events.\n{event_close, event_attention}\nevent_close - byte, can referenced as 0 - not triggered; 1- triggered if close crane 1 group; 2-if close crane 2 group; 3 - if close crane both group.\nevent_attention - byte, can referenced as 0 - not triggered; 1- triggered if attention on 1 group; 2- if attention on 2 group; 3 - if  both group.\n\n\n",
                     "name":"Relay settings",
                     "typeSpec":{
                        "type":"value",
                        "max":15,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":117,
                     "accessMode":"rw",
                     "code":"address_modbus",
                     "description":"Address MODBUS RTU for extender module RS-485. Defined in settings menu",
                     "name":"Address MODBUS",
                     "typeSpec":{
                        "type":"value",
                        "max":247,
                        "min":1,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":118,
                     "accessMode":"rw",
                     "code":"modbus_speed",
                     "description":"Settings speed of MODBUS RTU. Defined in settings menu",
                     "name":"MODBUS speed",
                     "typeSpec":{
                        "type":"enum",
                        "range":[
                           "1200",
                           "2400",
                           "4800",
                           "9600",
                           "19200",
                           "38400",
                           "57600",
                           "115200",
                           "230400",
                           "460800",
                           "921600"
                        ]
                     }
                  },
                  {
                     "abilityId":119,
                     "accessMode":"rw",
                     "code":"count_rf_sensors",
                     "description":"Counter of RF sensors. This value use for viewing window of configuration RF sensors.",
                     "name":"Count of RF sensors",
                     "typeSpec":{
                        "type":"value",
                        "max":50,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":120,
                     "accessMode":"rw",
                     "code":"settings_rf_sensors",
                     "description":"Massive of settings RF sensors.  Max size of massive is 50. \nSettings is defined control leakage on group.\n{settings_rf_sensors} - data as 3 bytes for each sensor.\nReferenced as:\naddress of sensor - 2 bytes\ngroup connection - 1 byte, referenced as:\n   1 -  first group\n   2 - second group\n   3 - both group",
                     "name":"Settings RF sensors",
                     "typeSpec":{
                        "type":"raw",
                        "maxlen":128
                     }
                  },
                  {
                     "abilityId":121,
                     "accessMode":"ro",
                     "code":"status_rf_sensor",
                     "description":"Massive of status RF sensors.  Max size of massive is 50. \nEach element of massive consist five bytes.\n{\n battery_level (ref. 0-100),\n rf_signal_level (0 - 4),\n lost_sensor flag (0-1),\n discharge_battery_flag (0-1),\n attention_leakage_flag (0-1)\n} \n",
                     "name":"Status RF sensors",
                     "typeSpec":{
                        "type":"raw",
                        "maxlen":128
                     }
                  },
                  {
                     "abilityId":122,
                     "accessMode":"rw",
                     "code":"counters_data",
                     "description":"Data of counters for four slots.\n-Status counter n.m - 0/1 (byte)\n-Value of counter n.m - 0 -99999999 (4 bytes)\n-Step of counter n.m - 1,10,100 (byte)\n-Type of connection n.m - 0/1 (no namur/namur) (byte);\n-Error counter n.m - 0 -not, 1-short circuit, 2-brake (byte);\nn.m - n-slot(1-4), m - counter (1-2).\n",
                     "name":"Counters data",
                     "typeSpec":{
                        "type":"raw",
                        "maxlen":128
                     }
                  },
                  {
                     "abilityId":123,
                     "accessMode":"ro",
                     "code":"notifications",
                     "description":"Fault for notifications",
                     "extensions":{
                        "scope":"fault"
                     },
                     "name":"Notifications",
                     "typeSpec":{
                        "type":"bitmap",
                        "label":[
                           "alarm_1_group",
                           "alarm_2_group",
                           "sens_discharge",
                           "sensor_lost",
                           "close_1_group",
                           "close_2_group",
                           "cleaning_floor",
                           "slot1_error",
                           "slot2_error",
                           "slot3_error",
                           "slot4_error",
                           "counter1_error",
                           "counter2_error",
                           "counter3_error",
                           "counter4_error",
                           "counter5_error",
                           "counter6_error",
                           "counter7_error",
                           "counter8_error"
                        ],
                        "maxlen":19
                     }
                  },
                  {
                     "abilityId":124,
                     "accessMode":"rw",
                     "code":"name_rf_1_5",
                     "description":"Name of sensors 1-5. Each name have size is 51 byte.",
                     "name":"Name RF sens 1-5",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":125,
                     "accessMode":"rw",
                     "code":"name_rf_6_10",
                     "description":"Names of senors 6-10. Each name have size is 51 byte.",
                     "name":"Name RF sens 6-10",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":126,
                     "accessMode":"rw",
                     "code":"name_rf_11_15",
                     "description":"Name of sensors 11-15. Each name have size is 51 byte.",
                     "name":"Name RF sens 11-15",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":127,
                     "accessMode":"rw",
                     "code":"name_rf_16_20",
                     "description":"Name of sensors 16-20",
                     "name":"Name RF sens 16-20",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":128,
                     "accessMode":"rw",
                     "code":"name_rf_20_25",
                     "description":"Name of sensors 20-25. Each name have size is 51 byte.",
                     "name":"Name RF sens 20-25",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":129,
                     "accessMode":"rw",
                     "code":"name_rf_26_30",
                     "description":"Name of sensors 26-30. Each name have size is 51 byte.",
                     "name":"Name RF sens 26-30",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":130,
                     "accessMode":"rw",
                     "code":"name_rf_31_35",
                     "description":"Name of sensors 31-35. Each name have size is 51 byte.",
                     "name":"Name RF sens 31-35",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":131,
                     "accessMode":"rw",
                     "code":"name_rf_36_40",
                     "description":"Name of sensors 36-40. Each name have size is 51 byte.",
                     "name":"Name RF sens 36-40",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":132,
                     "accessMode":"rw",
                     "code":"name_rf_40_45",
                     "description":"Name of sensors 40-50. Each name have size is 51 byte.",
                     "name":"Name RF sens 40-45",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":133,
                     "accessMode":"rw",
                     "code":"name_rf_46_50",
                     "description":"Name of sensors 45-50. Each name have size is 51 byte.",
                     "name":"Name RF sens 46-50",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":134,
                     "accessMode":"rw",
                     "code":"name_lines",
                     "description":"Name of wired lines. In array 4 names. Each name have size is 51 byte.",
                     "name":"Name of Lines",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":135,
                     "accessMode":"rw",
                     "code":"name_zones",
                     "description":"Name of zones. In array 2 names. Each name have size is 51 byte.",
                     "name":"Name of Zones",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":136,
                     "accessMode":"rw",
                     "code":"name_of_counters",
                     "description":"Names of counters. In array 8 names. Each name have size is 31 byte.",
                     "name":"Name of Counters",
                     "typeSpec":{
                        "type":"string",
                        "maxlen":255
                     }
                  },
                  {
                     "abilityId":137,
                     "accessMode":"ro",
                     "code":"counter1",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter1",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":138,
                     "accessMode":"ro",
                     "code":"counter2",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter2",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":139,
                     "accessMode":"ro",
                     "code":"counter3",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter3",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":140,
                     "accessMode":"ro",
                     "code":"counter4",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter4",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":141,
                     "accessMode":"ro",
                     "code":"counter5",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter5",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":142,
                     "accessMode":"ro",
                     "code":"counter6",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter6",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":143,
                     "accessMode":"ro",
                     "code":"counter7",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter7",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":144,
                     "accessMode":"ro",
                     "code":"counter8",
                     "description":"",
                     "extensions":{
                        "trigger":"direct"
                     },
                     "name":"Counter8",
                     "typeSpec":{
                        "type":"value",
                        "max":99999999,
                        "min":0,
                        "scale":0,
                        "step":1,
                        "unit":""
                     }
                  },
                  {
                     "abilityId":145,
                     "accessMode":"ro",
                     "code":"slots_map",
                     "description":"Slots map",
                     "name":"Slots map",
                     "typeSpec":{
                        "type":"raw",
                        "maxlen":128
                     }
                  }
               ]
            }
         ]
      }
   },
   "success":true,
   "t":1717499666837,
   "tid":"9adff8d8226311efb9a3028d4a92b5f0"
}

Automations

alias: Neptun - Unavailable
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_status_crane_group_1
    to: unavailable
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Neptun become unavailable
mode: single
alias: Neptun - Crane group is opened
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_status_crane_group_1
      - switch.neptun_status_crane_group_2
    from: "off"
    to: "on"
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Crane group is opened
mode: single
alias: Neptun - Crane group is closed
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_status_crane_group_1
      - switch.neptun_status_crane_group_2
    from: "on"
    to: "off"
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Crane group is closed
mode: single
alias: Neptun - Floor cleaning mode enabled
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_floor_cleaning
    from: "off"
    to: "on"
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Floor cleaning mode enabled
mode: single
alias: Neptun - Floor cleaning 1h notification
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_floor_cleaning
    to: "on"
    for:
      hours: 1
      minutes: 0
      seconds: 0
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Floor cleaning enabled 1 hour!
mode: single
alias: Neptun - Floor cleaning mode disabled
description: ""
trigger:
  - platform: state
    entity_id:
      - switch.neptun_floor_cleaning
    from: "on"
    to: "off"
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Floor cleaning mode disabled
mode: single
alias: Neptun - Leakage notification
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.neptun_notifications
    to: "3"
condition: []
action:
  - service: notify.notify
    data:
      title: Neptun Smart
      message: Leakage detected
mode: single
alias: Neptun - Leakage fixed notification
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.neptun_notifications
    to: "0"
    from: "3"
condition: []
action:
  - service: notify.notify
    data:
      message: Leakage fixed
      title: Neptun Smart
mode: single

I have tried to create device profile using tuya-local but it’s based on tinytuya and can get only DPS broadcasted by device (114,122,137-145). It looks like Tinytuya is only picking up seqno = 0 and localtuya is only picking up seqno = 1 and avoiding seqno = 0.

Received Payload: {'dps': {'114': 'AwMDAw=='}, 't': 1717564996}
Received Payload: {'dps': {'122': 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='}, 't': 1717564996}
Received Payload: {'dps': {'137': 0}, 't': 1717564996}
Received Payload: {'dps': {'138': 0}, 't': 1717564996}
Received Payload: {'dps': {'139': 0}, 't': 1717564996}
Received Payload: {'dps': {'140': 0}, 't': 1717564996}
Received Payload: {'dps': {'141': 0}, 't': 1717564996}
Received Payload: {'dps': {'142': 0}, 't': 1717564996}
Received Payload: {'dps': {'143': 0}, 't': 1717564996}
Received Payload: {'dps': {'144': 0}, 't': 1717564996}
Received Payload: {'dps': {'145': 'Bf///w=='}, 't': 1717564996}

If you know how to improve this integration please share you thoughts.

A for effort but, I avoid Tuya products personally. These are they types of situations where Esphome shines. Custom water leak sensor and what the hell, add a temp/humidity sensor, sound sensor, and make it double as a BT hub for presence detection if you want. Besides the obvious water sensor, the sound sensor can double as a leak detector by watching for a patter of drips and add it to alarm system as sound sensor or even use it to shut off lights if no sound or BT presence. Temp/humidity doubles as it’s primary use case but also for example you could use it to trigger the exhaust fan in your bathroom.

https://www.amazon.com/Contact-Detector-Applicable-Equipment-Station/dp/B0CPHTHT29/ref=sr_1_12_sspa?dib=eyJ2IjoiMSJ9.kfgl2RjmgJdM8xs4hPaNapt__6oD2w7_Inlgr-tyaQTVauSjcuPdE1JbU0mX7mzfNIKAxKHDKzHzv-0MS4s8FZqSEO7V12zWQKDXvqpQZ0qnOByMHGgiQzLlM9yJYMXM1ecKrJxxuyde1B34AU753yPa46cEennmhuFeBh4EgBvIClvhnqelOMwsQTPQzoAX_G682L3BjqTh2F4UCPfXt7KUaFPTXyY7Tcv22IjdESq4DHd8obGCLGfm3Cf9Oiqe-e7foLWRZA5zRp1l9ExMNVY_5OReMuKi6fiodMtAd3s.As0K8hdUgdRvVCgvzKfsf8m8ET6ZsiAmyjYpnupdBN8&dib_tag=se&keywords=water+leak+sensor&qid=1718881653&sr=8-12-spons&sp_csd=d2lkZ2V0TmFtZT1zcF9tdGY&psc=1

https://www.amazon.com/Floor-Water-Sensor-Flood-Detection/dp/B079YB1T8J/ref=pd_bxgy_thbs_d_sccl_1/138-3434974-0942357?pd_rd_w=0dyOH&content-id=amzn1.sym.c51e3ad7-b551-4b1a-b43c-3cf69addb649&pf_rd_p=c51e3ad7-b551-4b1a-b43c-3cf69addb649&pf_rd_r=AAYDAQMQ5S6PPZC4QRQ0&pd_rd_wg=9mmuR&pd_rd_r=027ae311-b5a6-4db2-bef6-5c0ccd6c930d&pd_rd_i=B079YB1T8J&th=1

DIY doesnt always look as pretty but, its far more versatile, useful and 100% customizable. This thing we call HA wouldn’t be as great as it is without people like you doing what you are doing but, some things just aren’t worth the trouble and IMO this is one of those. Although I don’t have any help to offer, I will say that I appreciate what you are trying to do.