Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge 🌉, get rid of your proprietary Zigbee bridges 🔨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
11.94k stars 1.66k forks source link

[New device support]: Garden Watering Timer #12778

Closed Andy-Hennah closed 2 years ago

Andy-Hennah commented 2 years ago

Link

https://www.aliexpress.com/item/1005004208466976.html?spm=a2g0o.productlist.0.0.36d07996Jv1iry&algo_pvid=c52efdc5-808c-4e9a-945f-bf0d7fa1401a&algo_exp_id=c52efdc5-808c-4e9a-945f-bf0d7fa1401a-0&pdp_ext_f=%7B%22sku_id%22%3A%2212000028385854560%22%7D&pdp_npi=2%40dis%21USD%21%2136.12%21%21%21%21%21%402100bb5116548771413708852e549b%2112000028385854560%21sea

Database entry

{"id":40,"type":"EndDevice","ieeeAddr":"0xa4c13881f9ac0b02","nwkAddr":41578,"manufId":4417,"manufName":"_TZE200_sh1btabb","powerSource":"Battery","modelId":"TS0601","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":81,"inClusterList":[4,5,61184,0],"outClusterList":[25,10],"clusters":{"genBasic":{"attributes":{"65503":"S76i�76f�76\u0012�76e","65506":54,"65508":0,"stackVersion":0,"dateCode":"","appVersion":70}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":70,"stackVersion":0,"hwVersion":1,"dateCode":"","zclVersion":3,"interviewCompleted":true,"meta":{},"lastSeen":1654877529881,"defaultSendRequestWhen":"immediate"}

Comments

I did try writing my own converter but it's a TuYa device and it's more complicated than adding a switch.

External converter

No response

Supported color modes

No response

Color temperature range

No response

Andy-Hennah commented 2 years ago

There are some unprintable characters in the Database Entry. Should I be opening database.db with a particular encoding? The data above is with UTF-8 encoding.

Andy-Hennah commented 2 years ago

This might relate to #12153 and #1551 They look like the same model ID (TS0601) but this one has a manufacturer name of _TZE200_sh1btabb

raf02 commented 2 years ago

This might relate to #12153 and #1551 They look like the same model ID (TS0601) but this one has a manufacturer name of _TZE200_sh1btabb

Those are different unfortunately. I've managed to collect a few of the data points through trial and error - and they don't match with those models

raf02 commented 2 years ago

I've started an integration. I don't have a hub at the moment to capture the correct messages to the device. However, it is able to read the current state and turn off the valve.

I'll try to complete it once my hub comes in and I can sniff out the correct commands to send to turn on the valve. I hoped it would just be setting a flag to turn the valve on, but it looks more complicated than that. I'm assuming it also needs a timer or water capacity value. (The valve does turn on when you sent the state to 'on' but then turns off right away)

'use strict';

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;

const giExDataPoints = {
    // Confirmed
    state: 2,                   // current state of the valve (open or not)
    water_consumed: 111,        // appears to be water value - reported in liter
    last_run_starttime: 101,    // last run start time -- reported in GMT+8 - need to figure out how to conver to local time
    last_run_endtime: 102,      // last run end time
    last_run_duration: 114,     // last run duration

    // To Be Determined
    battery: 108, //not sure if its this value but currently reading 100 percent
    mode: 106, //?? This is something but we don't know what -- could be mode?.  We know there is a mode, but don't know what the values are
    //103  desired volume? --switchable?
};

const fzLocal = {
    FZL1: 
    {
        cluster: 'manuSpecificTuya',
        type: ['commandDataResponse', 'commandDataReport'],
        convert: (model, msg, publish, options, meta) => {
            for (const dpValue of msg.data.dpValues) {
                const value = tuya.getDataValue(dpValue);
                const dp = dpValue.dp
                meta.logger.info(`RECEIVED DP #${dp} -- VALUE = ${value}`);

                switch (dp) {
                    case giExDataPoints.state: { //State
                        return {state: value ? 'ON': 'OFF'};
                    }
                    case giExDataPoints.water_consumed: {
                        return {water_consumed: (value).toFixed(1)};
                    }
                    case giExDataPoints.last_run_starttime: {
                        return {last_run_starttime: value};
                    }
                    case giExDataPoints.last_run_duration: {
                        // last run duration -- value reported in seconds
                        return {last_run_duration: value };
                    }
                    case giExDataPoints.mode: { //MODE???
                        if (value === 0) return {mode: 'disabled'};
                        else if (value === 1) return {mode: 'active'};
                        else return {mode: 'enabled'};
                    }
                    case giExDataPoints.last_run_endtime: {
                        return {last_run_endtime: value};
                    }
                    case giExDataPoints.battery: {  //not sure if its this value but currently reading 100 percent
                        return {battery: value};
                    }
                    default: {
                        meta.logger.warn(`zigbee-herdsman-converters:: NOT RECOGNIZED DP ` +
                            `#${dp} with data ${JSON.stringify(msg.data)} VALUE = ${value}`);
                    }
                }
            }

        },
    },
};

const tzLocal = {
    GIEX_timer: 
    {
        key: ['timer'],
        convertSet: async (entity, key, value, meta) => {
            // input in minutes with maximum of 600 minutes (equals 10 hours)
            const timer = 60 * Math.abs(Math.min(value, 600));
            // sendTuyaDataPoint* functions take care of converting the data to proper format
            await tuya.sendDataPointValue(entity, 11, timer, 'setData', 1);
            return {state: {timer: value}};
        },
    },
    GIEX_mode: 
    {
        key: ['mode'],
        convertSet: async (entity, key, value, meta) => {
            let mode = 0;
            if (value === 'disabled') mode = 0;
            else if (value === 'active') mode = 1;
            else mode = 2;

            await tuya.sendDataPointEnum(entity, giExDataPoints.mode, mode);
            return {state: {mode: value}};
        },
    },
    GIEX_state: {
        key: ['state'],
        convertSet: async (entity, key, value, meta) => {
            await tuya.sendDataPointBool(entity, giExDataPoints.state, value === 'ON');
        },
    },

};

const definition = {
    fingerprint: [
        {modelID: 'TS0601', manufacturerName: '_TZE200_sh1btabb'}
    ],
    model: 'TS0601',
    vendor: 'TuYa',
    description: 'Water valve',
    //onEvent: tuya.onEventSetLocalTime,
    fromZigbee: [
        fzLocal.FZL1,
        //fz.tuya_data_point_dump,
    ],
    toZigbee: [
        tzLocal.GIEX_timer,
        tzLocal.GIEX_mode,
        tzLocal.GIEX_state,
        tz.tuya_data_point_test,
    ],
    exposes: [
        e.battery(),
        exposes.binary('state', ea.STATE_SET, 'ON', 'OFF').withDescription('State'),
        exposes.numeric('last_run_starttime', ea.STATE).withUnit('GMT+8').withDescription('Last Run Start Time'),
        exposes.numeric('last_run_endtime', ea.STATE).withUnit('GMT+8').withDescription('Last Run End Time'),
        //exposes.enum('mode', ea.STATE_SET, ['disabled', 'active', 'enabled']),
        //exposes.numeric('timer', exposes.access.STATE_SET).withValueMin(0).withValueMax(240).withUnit('min')
        //  .withDescription('Countdown timer in minutes'),
        exposes.numeric('last_run_duration', exposes.access.STATE).withUnit('min')
            .withDescription('Last Run Duration'),
        exposes.numeric('water_consumed', exposes.access.STATE).withUnit('L')
            .withDescription('Last Run Water Consumed (L)'),

    ],
};

module.exports = definition;
Andy-Hennah commented 2 years ago

Hi @raf02. This is awesome, thanks. I've got a device here, it's not plumbed in but I can certainly switch it on and off from z2m. It's not turning off straight away like you are seeing but it is reverting to off after about a minute - I think that's expected behavior. I can do some more testing if you need me to.

raf02 commented 2 years ago

Hi @raf02. This is awesome, thanks. I've got a device here, it's not plumbed in but I can certainly switch it on and off from z2m. It's not turning off straight away like you are seeing but it is reverting to off after about a minute - I think that's expected behavior. I can do some more testing if you need me to.

What are you using to switch it on/off?

I actually have the device too. I just don't have a hub - so I don't know what the valid commands to send it are. I'm guessing (by looking at the pictures of the app in the instructions) that you need to pass it either a timer value or an amount to water. However, I won't know what those data points are until I get a hub and can trigger the commands.

Andy-Hennah commented 2 years ago

If I push the physical button on the device, I can see last_run_endtime flip to NULL and last_run_starttime goes to now (although the time is in GMT+8).

After 3 mins it hasn't switched off automatically so I pushed the button on the device again. This correctly updated last_run_endtime and last_run_duration.

If I use the z2m web interface to switch it on it switches off automatically after 1min.

Andy-Hennah commented 2 years ago

I don't have a hub either I'm afraid.

raf02 commented 2 years ago

So there's a few things left to figure out.

  1. How to send the 'turn on' command with the appropriate options (timer or water volume). I'll try this once I get my hub, but if anyone else wants to help, feel free to jump in.
  2. The device has an alarm feature if the valve is open but no water flowing. This still needs to be added to the z2m converter.
  3. How to convert the GMT+8 times to local time. I haven't run across any z2m documentation on how to treat times and timezones. I tried onEvent: tuya.onEventSetLocalTime but it didn't seem to do anything.
Andy-Hennah commented 2 years ago

Thanks so much @raf02 :-)

djurcola commented 2 years ago

Hi All,

Used the config @raf02 provided here: https://github.com/Koenkk/zigbee2mqtt/issues/12778#issuecomment-1155613836 and can switch on and off without delay unlike what @Andy-Hennah experienced.

last_run_duration and battery also seem to be updating correctly.

Thanks @raf02!

raf02 commented 2 years ago

An updated version. This one supports the timer or watering capacity. The timezone issue on the start and stop times still exists, but otherwise, it functions as you'd expect.

'use strict';

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const e = exposes.presets;
const ea = exposes.access;

const giExDataPoints = {
    // Confirmed
    state: 2,                   // current state of the valve (close=0 open = 1)
    mode: 1,                    // 0=duration 1=capacity
    irrigation_target: 104,     // duration in min - or capacity in liters
    water_consumed: 111,        // real-time water usage (liters)
    irrigation_start_time: 101,     // last run start time -- reported in GMT+8 - need to figure out how to conver to local time
    irrigation_end_time: 102,       // last run end time
    last_irrigation_duration: 114,      // last run duration
    battery: 108, //batter percentage

    // For cycle irrigation
    cycle_irrigation_num_times: 103,    //number of times to repeat cycle.  0=single irrigation
    cycle_irrigation_interval: 105,     //real-time irrigation interval (seconds)

    // Other data points.  Usage unclear
    current_tempurature: 106,  // Listed as current temp in the spec.  Only see it publish value 0 once capacity mode finishes.  Strange
    app_start_switch: 112, // Called 'other extentions' in the spec.  appear to be set to true when app triggers start
    smart_weather:107,  // Can be used in native app to implement rain delay
    realtime_cumulative_duration: 110,      //seconds
};

const fzLocal = {
    FZL1: 
    {
        cluster: 'manuSpecificTuya',
        type: ['commandDataResponse', 'commandDataReport'],
        convert: (model, msg, publish, options, meta) => {
            for (const dpValue of msg.data.dpValues) {
                const value = tuya.getDataValue(dpValue);
                const dp = dpValue.dp
                meta.logger.debug(`RECEIVED DP #${dp} -- VALUE = ${value}`);

                switch (dp) {
                    case giExDataPoints.state: { //State
                        return {state: value ? 'ON': 'OFF'};
                    }
                    case giExDataPoints.mode: { //MODE
                        return {mode: value ? 'Capacity': 'Duration'};
                    }
                    case giExDataPoints.irrigation_target: {
                        return {irrigation_target: value };
                    }
                    case giExDataPoints.cycle_irrigation_num_times: {
                        return {cycle_irrigation_num_times: value };
                    }
                    case giExDataPoints.cycle_irrigation_interval: {
                        return {cycle_irrigation_interval: value };
                    }
                    case giExDataPoints.water_consumed: {
                        return {water_consumed: value};
                    }
                    case giExDataPoints.irrigation_start_time: {
                        return {irrigation_start_time: value};
                    }
                    case giExDataPoints.irrigation_end_time: {
                        return {irrigation_end_time: value};
                    }
                    case giExDataPoints.last_irrigation_duration: {
                        return {last_irrigation_duration: value };
                    }
                    case giExDataPoints.battery: {
                        return {battery: value};
                    }
                    case giExDataPoints.current_tempurature: {
                        return; //Do Nothing since we don't know what this value means
                    }
                    default: {
                        meta.logger.warn(`zigbee-herdsman-converters:: NOT RECOGNIZED DP ` +
                            `#${dp} with data ${JSON.stringify(msg.data)} VALUE = ${value}`);
                    }
                }
            }

        },
    },
};

const tzLocal = {
    GIEX_mode: 
    {
        key: ['mode'],
        convertSet: async (entity, key, value, meta) => {
            let mode = 0;
            if (value === 'Duration') mode = 0;
            else if (value === 'Capacity') mode = 1;
            await tuya.sendDataPointBool(entity, giExDataPoints.mode, mode);
            return {state: {mode: value}};
        },
    },
    GIEX_timer: 
    {
        key: ['irrigation_target'],
        convertSet: async (entity, key, value, meta) => {
            // input in minutes with maximum of 1440 minutes (equals 24 hours)
            await tuya.sendDataPointValue(entity, giExDataPoints.irrigation_target, value);
            return {state: {irrigation_target: value}};
        },
    },
    GIEX_state: {
        key: ['state'],
        convertSet: async (entity, key, value, meta) => {
            await tuya.sendDataPointBool(entity, giExDataPoints.state, value === 'ON');
        },
    },
    GIEX_cycle_irrigation_times: {
        key: ['cycle_irrigation_num_times'],
        convertSet: async (entity, key, value, meta) => {
            await tuya.sendDataPointValue(entity, giExDataPoints.cycle_irrigation_num_times, value);
            return {state: {cycle_irrigation_num_times: value}};
        },
    },
    GIEX_cycle_irrigation_interval: {
        key: ['cycle_irrigation_interval'],
        convertSet: async (entity, key, value, meta) => {
            await tuya.sendDataPointValue(entity, giExDataPoints.cycle_irrigation_interval, value);
            return {state: {cycle_irrigation_interval: value}};
        },
    },
};

const definition = {
    fingerprint: [
        {modelID: 'TS0601', manufacturerName: '_TZE200_sh1btabb'}
    ],
    model: 'TS0601',
    vendor: 'GiEX',
    description: 'Water Irrigation Valve',
    onEvent: tuya.onEventSetLocalTime,  //onEvent: tuya.onEventSetTime,
    fromZigbee: [
        fzLocal.FZL1,
    ],
    toZigbee: [
        tzLocal.GIEX_timer,
        tzLocal.GIEX_mode,
        tzLocal.GIEX_state,
        tzLocal.GIEX_cycle_irrigation_times,
        tzLocal.GIEX_cycle_irrigation_interval,
        tz.tuya_data_point_test,
    ],
    exposes: [
        e.battery(),
        exposes.binary('state', ea.STATE_SET, 'ON', 'OFF').withDescription('State'),
        exposes.enum('mode', ea.STATE_SET, ['Duration', 'Capacity']).withDescription('Irrigation Mode'),
        exposes.numeric('irrigation_target', exposes.access.STATE_SET).withValueMin(0).withValueMax(1440).withUnit('min or L')
            .withDescription('Irrigation Target. Duration in minutes - or Capacity in Liters (depending on mode)'),
        exposes.numeric('cycle_irrigation_num_times', exposes.access.STATE_SET).withValueMin(0).withValueMax(100).withUnit('# of times')
            .withDescription('Cycle Irrigation Times.  Set to 0 for single cycle'),
        exposes.numeric('cycle_irrigation_interval', exposes.access.STATE_SET).withValueMin(0).withValueMax(1440).withUnit('min')
            .withDescription('Cycle Irrigation Interval'),
        exposes.numeric('irrigation_start_time', ea.STATE).withUnit('GMT+8').withDescription('Irrigation Start Time'),
        exposes.numeric('irrigation_end_time', ea.STATE).withUnit('GMT+8').withDescription('Irrigation End Time'),
        exposes.numeric('last_irrigation_duration', exposes.access.STATE).withUnit('min')
            .withDescription('Last Irrigation Duration'),
        exposes.numeric('water_consumed', exposes.access.STATE).withUnit('L')
            .withDescription('Water Consumed (L)'),

        //exposes.numeric('current_tempurature', ea.STATE).withUnit('C')
        //  .withDescription('Current Tempurature'),

    ],
};

module.exports = definition;
Andy-Hennah commented 2 years ago

looks good @raf02 - it's all working as expected for me.

Koenkk commented 2 years ago

Merged, assuming this can be closed now.

Changes will be available in the dev branch in a few hours from now. (https://www.zigbee2mqtt.io/advanced/more/switch-to-dev-branch.html)

ShafiqUrRehman commented 2 years ago

Hello, Thank you for the update.

If water is not flowing through the valve, then the mobile app give the warning. Any idea how such reading can be captured here in the code?

Thanks,

raf02 commented 2 years ago

Hello, Thank you for the update.

If water is not flowing through the valve, then the mobile app give the warning. Any idea how such reading can be captured here in the code?

Thanks,

Unfortunately, the device itself isn't emitting this warning message (as far as I can tell). I think their mobile app is deducing it based on the fact that valve is open but the water consumption isn't increasing. You'd have to do it a similar way in whatever automation platform you're using.

fredericgermain commented 2 years ago

Thanks for the awesome support!

I just did a quick test for 1L, and I got 916ml. I'm not sure if it could be improved? Anyway, I don't need so much precision, just to let you know.

dherykw commented 1 year ago

Hello guys,

The app allows users to set the schedule for irrigation, I have scheduled some irrigations and then I disconnected the device from the network.

It seems that all scheduled irrigations have been properly executed without connectivity.

Do you think that this behavior could be achieved through zigbee2mqtt?

raf02 commented 1 year ago

Thanks for the awesome support!

I just did a quick test for 1L, and I got 916ml. I'm not sure if it could be improved? Anyway, I don't need so much precision, just to let you know.

I don't think the device is accurate enough to get a better reading.

raf02 commented 1 year ago

Hello guys,

The app allows users to set the schedule for irrigation, I have scheduled some irrigations and then I disconnected the device from the network.

It seems that all scheduled irrigations have been properly executed without connectivity.

Do you think that this behavior could be achieved through zigbee2mqtt?

I didn't see any commands going to the device when I manipulated the scheduling settings. I assumed it was done though the app itself - or maybe through the hub? In either case, I didn't see a way it could be done in z2m, but maybe someone else has some ideas.

I suspect most people want to do scheduling through their own automation solution (eg. Home Assistant) since it would allow for much more customization (eg. based on weather, wind, moisture sensors, etc) so i didn't spend a ton of time on that feature.

dherykw commented 1 year ago

Hello guys, The app allows users to set the schedule for irrigation, I have scheduled some irrigations and then I disconnected the device from the network. It seems that all scheduled irrigations have been properly executed without connectivity. Do you think that this behavior could be achieved through zigbee2mqtt?

I didn't see any commands going to the device when I manipulated the scheduling settings. I assumed it was done though the app itself - or maybe through the hub? In either case, I didn't see a way it could be done in z2m, but maybe someone else has some ideas.

I suspect most people want to do scheduling through their own automation solution (eg. Home Assistant) since it would allow for much more customization (eg. based on weather, wind, moisture sensors, etc) so i didn't spend a ton of time on that feature.

Really true that people want to use HA to schedule the watering but if you lose the connectivity and you are on a trip your plant would remain watering if the device worked in an autonomous way.

Anyway... thank a los lot for your response