Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[New device support]: Zemismart Curtain Robot motor ZM85EL-2Z #11251

Closed
little-dgek opened this issue Feb 5, 2022 · 45 comments
Closed

[New device support]: Zemismart Curtain Robot motor ZM85EL-2Z #11251

little-dgek opened this issue Feb 5, 2022 · 45 comments
Labels
new device support New device support request stale Stale issues

Comments

@little-dgek
Copy link

little-dgek commented Feb 5, 2022

Link

https://www.zemismart.com/products/zm85el-2z

Database entry

{ "id": 8, "type": "EndDevice", "ieeeAddr": "0x94deb8fffe25058b", "nwkAddr": 16394, "manufId": 4098, "manufName": "_TZE200_cf1sl3tj", "powerSource": "Battery", "modelId": "TS0601", "epList": [1], "endpoints": { "1": { "profId": 260, "epId": 1, "devId": 81, "inClusterList": [0, 4, 5, 61184], "outClusterList": [25, 10], "clusters": { "genBasic": { "attributes": { "modelId": "TS0601", "manufacturerName": "_TZE200_cf1sl3tj", "powerSource": 3, "zclVersion": 3, "appVersion": 72, "stackVersion": 0, "hwVersion": 1, "dateCode": "" } } }, "binds": [{ "cluster": 0, "type": "endpoint", "deviceIeeeAddress": "0x00124b0022812c80", "endpointID": 1 }, { "cluster": 258, "type": "endpoint", "deviceIeeeAddress": "0x00124b0022812c80", "endpointID": 1 }], "configuredReportings": [], "meta": {} } }, "appVersion": 72, "stackVersion": 0, "hwVersion": 1, "dateCode": "", "zclVersion": 3, "interviewCompleted": true, "meta": { "configured": -1267545107 }, "lastSeen": 1644093139243, "defaultSendRequestWhen": "immediate" }

Comments

Hello. Please, help me.
I tried to follow guide, and almost win)

I used Tuya model (TS0601_cover) and I have only one warning:

_TuYa_cover_control: Unhandled DP #13 for TZE200_cf1sl3tj: {"dp":13,"datatype":2,"data":{"type":"Buffer","data":[0,0,0,95]}}

And 1 error:
Error No converter available for 'get' 'position' (0)

External converter

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 e = exposes.presets;
const ea = exposes.access;
const tuya = require("zigbee-herdsman-converters/lib/tuya");

const definition = {
    fingerprint: [
        {
            modelID: 'TS0601',
            manufacturerName: '_TZE200_cf1sl3tj'
        },
    ],
    model: 'ZM85EL-2Z',
    vendor: 'Zemismart',
    description: 'Curtain motor/roller blind motor/window pusher/tubular motor',
    supports: 'curtains, blinder',

    fromZigbee: [fz.tuya_cover, fz.ignore_basic_report],
    toZigbee: [tz.tuya_cover_control, tz.tuya_cover_options],
    exposes:[ e.cover_position().setAccess('position', ea.STATE_SET),
              exposes.composite('options', 'options')
                .withFeature(exposes.numeric('motor_speed', ea.STATE_SET)
                    .withValueMin(0)
                    .withValueMax(255)
                    .withDescription('Motor speed'))],
    meta: {multiEndpoint: true},


    onEvent: tuya.onEventSetTime, // Add this if you are getting no converter for 'commandMcuSyncTime'
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await reporting.bind(endpoint, coordinatorEndpoint, ['closuresWindowCovering']);
    },
};

module.exports = definition;

Supported color modes

No response

Color temperature range

No response

@little-dgek little-dgek added the new device support New device support request label Feb 5, 2022
@D-side
Copy link

D-side commented Feb 21, 2022

Hi!
I have the same device and intend to get it to work.
Zemismart are apparently nice enough to provide protocol documentation upon request, maybe we could try that method, it would save some time.

Here's the converter file I have so far:

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 definition = {
    fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_cf1sl3tj'}],
    model: 'ZM85EL-2Z',
    vendor: 'Zemismart',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fz.tuya_cover, fz.ignore_basic_report],
    toZigbee: [tz.tuya_cover_control],
    exposes: [
        exposes.cover(),
    ],
};

module.exports = definition;

I started from the same TS0601_cover template as you, but stripped the parts I wasn't certain about, including the cover position you seem to be getting an error message about, might help you as well.

I can confirm that with this converter the device properly functions in Home Assistant with ⬆⏹⬇ buttons, which is enough for me to do some basic automation. 🎉 Though I do still observe the warning about DP#13.

Things we can also hopefully find:

  • Battery state (could very well be that DP#13, but I've yet to deplete the battery significantly to observe whether the values change; so far I've seen the last byte be 80, 85 and 90, but not 95 like you are; battery voltage can fluctuate somewhat I guess)
  • Limit calibration (though I recall seeing a comment that it may be exclusive to the RF remote, so hopes are low here)

@github-actions
Copy link
Contributor

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

@github-actions github-actions bot added the stale Stale issues label Mar 24, 2022
@D-side
Copy link

D-side commented Mar 24, 2022

Still valid, haven't been able to get on this so far, but the converter I've provided so far has been working with no issues.
Not that there's a lot to have issues with in it, given how stripped-down it is… 😁

@nchieng
Copy link

nchieng commented Mar 24, 2022

Thanks for the work on this.
Just wanting to shout out that this issue is the only thing stopping me from ordering the device.
Would love to see a working converter make it into a PR.

@github-actions github-actions bot removed the stale Stale issues label Mar 25, 2022
@brunoap
Copy link

brunoap commented Apr 5, 2022

It is working, hence there are some errors on the logs, which i turned to info only. Thanks for the work on this.

@christiangenco
Copy link

This was the first extension I've installed in zigbee2mqtt. It was kinda tricky for me to figure out where the converters go but I got it working!

 2022-04-16 at 3 08 42 PM

I was able to get OPEN, CLOSE, and STOP buttons on the web interface by following these steps:

  1. Copy @D-side's converter code above. @little-dgek's code gave me extra options for position and motor speed but I can't figure out how to get those options to do anything.
  2. Paste that code in a new file called TS0601.js in your data directory (the same directory as configuration.yaml. For me the full path of that file is /opt/zigbee2mqtt/data/TS0601.js.
  3. Add these two lines to configuration.yaml:
external_converters: 
  - TS0601.js
  1. Restart zigbee2mqtt. I'm running zigbee2mqtt as a service so I restart it with sudo systemctl restart zigbee2mqtt.service.

Every time I send a command I'm seeing errors that look like Warning TuYa_cover_control: Unhandled DP #13 for _TZE200_cf1sl3tj: {"dp":13,"datatype":2,"data":{"type":"Buffer","data":[0,0,0,50]}} but at least the motor is running!

@nchieng
Copy link

nchieng commented Apr 19, 2022

I would love to be able to figure out how to configure the cover position limits via MQTT. Which Datapoint and what values they are.

Does anyone have this device and a Zemismart hub, setup with Tuya?
Could anyone find the options => data point mapping?

https://www.zigbee2mqtt.io/advanced/support-new-devices/03_find_tuya_data_points.html#_8-display-device-logs

@Oliviakrkk
Copy link

I will try to find it. I was able to setup developer account:
image

@Oliviakrkk
Copy link

Current curtain position: code 3
Motor direction: code 5
Border: 16
Control 1
Curtain position setting 2
Best position 19
Mode 4
Click control 20
Motor direction 5
Work state 7
Situation set 17
Fult 12
Battery 13

@nchieng Will this info suffice?

@D-side
Copy link

D-side commented Apr 22, 2022

@Oliviakrkk oh, so 13 is the battery! That's very helpful to know.
In addition to the numbers of the data points I reckon we would also need the data types for each to be able to use them. For the battery it's obvious, others can be guessed given their purposes, but more reliable info would accelerate the process significantly.

@Oliviakrkk
Copy link

I have issues with finding more info...In the request response there is a field:

eventDetail

And when it comes to battery it just shows the value...

{"result":{"devId":"XXX","datas":[{"devId":"XXX","eventType":"Report","sourceDetail":"","eventTime":1650626326586,"eventName":"Battery Percentage","eventTimeStr":"2022-04-22 11:18:46:586","eventDetail":"85%","requestFrom":"device itself"}],"docType":1,"currentPageStartRowKey":"XXX=="},"t":1650628858171,"success":true,"status":"ok"}

Is that what we need?

@Oliviakrkk
Copy link

Oliviakrkk commented Apr 22, 2022

I also found some response called status:

{"result":[{"code":"control","value":"open"},{"code":"percent_control","value":100},{"code":"percent_state","value":100},{"code":"mode","value":"morning"},{"code":"control_back_mode","value":"forward"},{"code":"work_state","value":"opening"},{"code":"situation_set","value":"fully_open"},{"code":"fault","value":0},{"code":"battery_percentage","value":85},{"code":"border","value":"up"},{"code":"position_best","value":0},{"code":"click_control","value":"up"}],"success":true,"t":1650629290063,"tid":"xxx"}

This may be usefull.

But still no field data_type...

@D-side
Copy link

D-side commented Apr 22, 2022

Most likely because the interface you're dealing with converts these types to more "semantically pure" (as in "without implementation details") values under the hood, without disclosing what came in originally, and zigbee2mqtt needs to do the same.

There's this bit in the original message that I observed with my device as well:

{"dp":13,"datatype":2,"data":{"type":"Buffer","data":[0,0,0,95]}}

Which likely means DP#13 is of type value (terminology from here). I'm not sure if 2 beside this type stands for anything or it's internal to this project only.

@Oliviakrkk
Copy link

To get this info from Toya I would need to sniff the zigbee network, right? And check payload in the wireshark...
Currently I do not have a sniffer :/

@D-side
Copy link

D-side commented Apr 22, 2022

And I only just now opened the guide above to realize this is Tuya's own interface; it doing this kind of conversion on its end makes sense. Yeah, I'm new to this. 😅

@Oliviakrkk not necessarily Wireshark, zigbee2mqtt has some built-in facilities. This would seem to be the relevant guide: https://www.zigbee2mqtt.io/advanced/support-new-devices/02_support_new_tuya_devices.html#_4-deciphering-the-data-points
You've already made a major step forward already by mapping data points to their respective purposes. What's left may still be guesswork, but within a much more manageable space.

I'll try to allocate some time this or next week to wrap my head around introducing mappings for more DPs, but totally wouldn't mind anyone beating me to it 😉

@D-side
Copy link

D-side commented Apr 22, 2022

little-dgek's code gave me extra options for position and motor speed but I can't figure out how to get those options to do anything.

@christiangenco I believe they were copied from another device which uses a different set of data points; but enough of it worked (motor controls, the main part) to seem like it fits overall, even if some parts don't.
I started the same way, but just in case I removed the bits that didn't seem to have an effect to avoid unnecessarily randomizing the behavior of the device and misinforming the user in the UI while we learn what the rest of the protocol means.

@rodrigogbs
Copy link

Hey guys, can you share the external converter that shows up the battery percentage?

@github-actions
Copy link
Contributor

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

@github-actions github-actions bot added the stale Stale issues label Jun 20, 2022
@D-side
Copy link

D-side commented Jun 21, 2022

Alright, alright! Not stale, just very slow! Cheesus 😅

@rodrigogbs I went in expecting an easy win, but left with a slightly better understanding how the project works and yet another reminder to check the versions, as I was often referencing the source code of the master branches rather than the versions I currently use (hassio addon, ZB2MQTT 1.18.1, with converters v14.0.74).

  • Battery charge is reported from DP 13, as per the warning at the start of the thread — but it took me a while to figure out that it's only reported when I push buttons on the remote, how often it reports the charge outside of that isn't clear to me
  • I copied the existing fz.tuya_cover converter and truncated it, because the "Unhandled DP" thing apparently reports everything unhandled, even if it is actually handled in another converter (maybe I don't understand how it works?)
  • In my testing position hasn't reported anything other than a zero, so I removed handling of that field and dependent on it state for now — maybe if I calibrate the motor they'll start working, but I have a hardware problem there, I'm running the motor on a very jury-rigged mounting mechanism that doesn't provide quite enough friction for it to reliably move along the rail so I don't expect any numbers there to be reliable; but if you observe meaningful values in position do let me know and I'll return that bit 🙂
  • Still have to go through @Oliviakrkk's list of datapoints to make sure all capabilities of the device are covered

Here's what the converter looks like now:

const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const tuya = require('zigbee-herdsman-converters/lib/tuya');

const fzDeviceSpecific = {
    cluster: 'manuSpecificTuya',
    type: ['commandSetDataResponse', 'commandGetData'],
    convert: (model, msg, publish, options, meta) => {
        const dp = msg.data.dp;
        const value = tuya.getDataValue(msg.data.datatype, msg.data.data);

        switch (dp) {
        case tuya.dataPoints.state: // Confirm opening/closing/stopping (triggered from Zigbee)
        case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
        case tuya.dataPoints.coverChange: // Started moving (triggered by transmitter or pulling on curtain)
        case tuya.dataPoints.coverArrived: { // Arrived at position
            const running = dp === tuya.dataPoints.coverArrived ? false : true;
            return {running}; // Position does not seem to be working, so respective code removed
        }
        case tuya.dataPoints.config: // Returned by configuration set; ignore
            break;
        case 13: // Battery; apparently only sent when triggered via hardware remote?
            return {battery: value};
        default: // Unknown code
            meta.logger.warn(`ZM85EL-2Z: Unhandled DP #${dp}: ${JSON.stringify(msg.data)}`);
        }
    },
}

const definition = {
    fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_cf1sl3tj'}],
    model: 'ZM85EL-2Z',
    vendor: 'Zemismart',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fzDeviceSpecific],
    toZigbee: [tz.tuya_cover_control],
    exposes: [
        exposes.cover(),
        exposes.presets.battery(),
    ],
    meta: {
        configureKey: 1,
        battery: {dontDividePercentage: true}
    },
};

module.exports = definition;

@github-actions github-actions bot removed the stale Stale issues label Jun 22, 2022
@rodrigogbs
Copy link

Alright, alright! Not stale, just very slow! Cheesus 😅

@rodrigogbs I went in expecting an easy win, but left with a slightly better understanding how the project works and yet another reminder to check the versions, as I was often referencing the source code of the master branches rather than the versions I currently use (hassio addon, ZB2MQTT 1.18.1, with converters v14.0.74).

  • Battery charge is reported from DP 13, as per the warning at the start of the thread — but it took me a while to figure out that it's only reported when I push buttons on the remote, how often it reports the charge outside of that isn't clear to me
  • I copied the existing fz.tuya_cover converter and truncated it, because the "Unhandled DP" thing apparently reports everything unhandled, even if it is actually handled in another converter (maybe I don't understand how it works?)
  • In my testing position hasn't reported anything other than a zero, so I removed handling of that field and dependent on it state for now — maybe if I calibrate the motor they'll start working, but I have a hardware problem there, I'm running the motor on a very jury-rigged mounting mechanism that doesn't provide quite enough friction for it to reliably move along the rail so I don't expect any numbers there to be reliable; but if you observe meaningful values in position do let me know and I'll return that bit 🙂
  • Still have to go through @Oliviakrkk's list of datapoints to make sure all capabilities of the device are covered

Here's what the converter looks like now:

const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const tuya = require('zigbee-herdsman-converters/lib/tuya');

const fzDeviceSpecific = {
    cluster: 'manuSpecificTuya',
    type: ['commandSetDataResponse', 'commandGetData'],
    convert: (model, msg, publish, options, meta) => {
        const dp = msg.data.dp;
        const value = tuya.getDataValue(msg.data.datatype, msg.data.data);

        switch (dp) {
        case tuya.dataPoints.state: // Confirm opening/closing/stopping (triggered from Zigbee)
        case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
        case tuya.dataPoints.coverChange: // Started moving (triggered by transmitter or pulling on curtain)
        case tuya.dataPoints.coverArrived: { // Arrived at position
            const running = dp === tuya.dataPoints.coverArrived ? false : true;
            return {running}; // Position does not seem to be working, so respective code removed
        }
        case tuya.dataPoints.config: // Returned by configuration set; ignore
            break;
        case 13: // Battery; apparently only sent when triggered via hardware remote?
            return {battery: value};
        default: // Unknown code
            meta.logger.warn(`ZM85EL-2Z: Unhandled DP #${dp}: ${JSON.stringify(msg.data)}`);
        }
    },
}

const definition = {
    fingerprint: [{modelID: 'TS0601', manufacturerName: '_TZE200_cf1sl3tj'}],
    model: 'ZM85EL-2Z',
    vendor: 'Zemismart',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fzDeviceSpecific],
    toZigbee: [tz.tuya_cover_control],
    exposes: [
        exposes.cover(),
        exposes.presets.battery(),
    ],
    meta: {
        configureKey: 1,
        battery: {dontDividePercentage: true}
    },
};

module.exports = definition;

Tks @D-side, amazing job!

I tried and it is working pretty well, despite the fact that the battery percentage is still not appearing... :(

The status behavior is working very well !!

About the position value (you're right, to have this is necessary to set limits with a remote control), but in this version even with limits set it is not appearing. This information is important to know if the cover is totally or partially opened, I use this in my automations, is it possible to have back?

@D-side
Copy link

D-side commented Jun 22, 2022

@rodrigogbs did the position work in my initial version above? If so, returning some of the code I removed after copying fz.tuya_cover from 14.0.74 should make it work. I'll get to it eventually, but it might take a while.

As I mentioned, the battery percentage right now only shows up after pushing a button on the remote, e. g. ⏹ stop. I guess to get it to show up more reliably I have to send a certain command to it during initialization, which right now is slightly beyond my level of familiarity with this codebase.

@rodrigogbs
Copy link

@rodrigogbs did the position work in my initial version above? If so, returning some of the code I removed after copying fz.tuya_cover from 14.0.74 should make it work. I'll get to it eventually, but it might take a while.

As I mentioned, the battery percentage right now only shows up after pushing a button on the remote, e. g. ⏹ stop. I guess to get it to show up more reliably I have to send a certain command to it during initialization, which right now is slightly beyond my level of familiarity with this codebase.

@D-side yes, the position was working in the initial version, I will try to return the partial code...

About the battery, even pushing some remote buttons, it didn't work...at least for me...

@smarthomejunkie
Copy link

Thank you for this. I've got it to work thanks to you. In the beginning it did not work, because my Manufacturer Name is: _TZE200_nw1r9hp6

Which is different than your name. When I changed it to the Manufacturer name that I found in database.db it worked immediately. Only the battery level is n/a for some reason. I will investigate this further to see if I can find a solution for this. Is it possible to add this curtain robot to the official Z2M supported devices?

@github-actions
Copy link
Contributor

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days

@github-actions github-actions bot added the stale Stale issues label Jul 28, 2022
@mrammal
Copy link

mrammal commented Sep 27, 2022

@DariBer question for you, does the ZM25RX-0.8/30 have soft stop functionality? My existing non-zigbee motors reduce speed slightly when it comes close to a stop.

@DariBer
Copy link

DariBer commented Sep 27, 2022

@DariBer question for you, does the ZM25RX-0.8/30 have soft stop functionality? My existing non-zigbee motors reduce speed slightly when it comes close to a stop.

I haven't noticed anything like that on non of my motor rollers. It use one speed and then just stop. So no reduced speed.

@jumpinf00l
Copy link

Just stumbled upon this tonight. I've fiddled with this a bit with some inspiration from the built-in converters - I've gotten DP#13 working as the battery percentage. It seems like this isn't calculated very accurately or at least mine seems to vary quite wildly (as low as 0% sometimes). Maybe we can average this value out? Position and state seem to work nicely as well now.

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 e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const utils = require('zigbee-herdsman-converters/lib/utils');
const globalStore = require('zigbee-herdsman-converters/lib/store');

const fzLocal = {
    device_specific: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse'],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {
            // Protocol description
            // https://github.com/Koenkk/zigbee-herdsman-converters/issues/1159#issuecomment-614659802

            const dpValue = tuya.firstDpValue(msg, meta, 'tuya_cover');
            const dp = dpValue.dp;
            const value = tuya.getDataValue(dpValue);

            switch (dp) {
            case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
            case tuya.dataPoints.coverArrived: { // Arrived at position
                const invert = tuya.isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                const position = invert ? 100 - (value & 0xFF) : (value & 0xFF);
                const running = dp !== tuya.dataPoints.coverArrived;

                // Not all covers report coverArrived, so set running to false if device doesn't report position for a few seconds
                clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer'));
                if (running) {
                    const timer = setTimeout(() => publish({running: false}), 3 * 1000);
                    globalStore.putValue(msg.endpoint, 'running_timer', timer);
                }

                if (position > 0 && position <= 100) {
                    return {running, position, state: 'OPEN'};
                } else if (position == 0) { // Report fully closed
                    return {running, position, state: 'CLOSE'};
                } else {
                    return {running}; // Not calibrated yet, no position is available
                }
            }
            case tuya.dataPoints.coverSpeed: // Cover is reporting its current speed setting
                return {motor_speed: value};
            case tuya.dataPoints.state: // Ignore the cover state, it's not reliable between different covers!
            case tuya.dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
                break;
            case tuya.dataPoints.config: // Returned by configuration set; ignore
                break;
            case 13: // Battery; apparently only sent when triggered via hardware remote?
                return {battery: value};
            default: // Unknown code
                meta.logger.warn(`TuYa_cover_control: Unhandled DP #${dp} for ${meta.device.manufacturerName}:
                ${JSON.stringify(dpValue)}`);
            }
        },
    },
};

const definition = {
    fingerprint: [
        {modelID: 'TS0601', manufacturerName: '_TZE200_68nvbio9'},
        {modelID: 'TS0601', manufacturerName: '_TZE200_cf1sl3tj'},
    ],
    model: 'TS0601_cover',
    vendor: 'TuYa',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fzLocal.device_specific, fz.ignore_basic_report],
    toZigbee: [tz.tuya_cover_control, tz.tuya_cover_options],
    exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.battery()],
    meta: {
        configureKey: 1,
        battery: {dontDividePercentage: true}
    },
};

module.exports = definition;```

@gbeleris
Copy link

gbeleris commented Oct 4, 2022

Hey, do you think this will be integrated in the next version?

I tried to set up an external converter, but even though it seems quite straight forward I got a bunch of errors and Z2M was not starting. I am running Z2M on a seperate host from HA.

Thanks!

@gbeleris
Copy link

@Koenkk @jumpinf00l is it possible to have this converter integrated in the next version please?

@Hamper
Copy link

Hamper commented Oct 18, 2022

in #11251 (comment) converter code must be line

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');

because it used in code.

@jumpinf00l
Copy link

Oops you're right @Hamper, I'd missed that line when copying+pasting. @gbeleris try this:

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 e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const utils = require('zigbee-herdsman-converters/lib/utils');
const globalStore = require('zigbee-herdsman-converters/lib/store');

const fzLocal = {
    device_specific: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse'],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {

            const dpValue = tuya.firstDpValue(msg, meta, 'tuya_cover');
            const dp = dpValue.dp;
            const value = tuya.getDataValue(dpValue);

            switch (dp) {
            case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
            case tuya.dataPoints.coverArrived: { // Arrived at position
                const invert = tuya.isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                const position = invert ? 100 - (value & 0xFF) : (value & 0xFF);
                const running = dp !== tuya.dataPoints.coverArrived;
                // Not all covers report coverArrived, so set running to false if device doesn't report position for a few seconds
                clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer'));
                if (running) {
                    const timer = setTimeout(() => publish({running: false}), 3 * 1000);
                    globalStore.putValue(msg.endpoint, 'running_timer', timer);
                }
                if (position > 0 && position <= 100) {
                    return {running, position, state: 'OPEN'};
                } else if (position == 0) { // Report fully closed
                    return {running, position, state: 'CLOSE'};
                } else {
                    return {running}; // Not calibrated yet, no position is available
                }
            }
            case tuya.dataPoints.coverSpeed: // Cover is reporting its current speed setting
                return {motor_speed: value};
            case tuya.dataPoints.state:
            case tuya.dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
                break;
            case tuya.dataPoints.config: // Returned by configuration set; ignore
                break;
            case 13: // Battery
                return {battery: value};
            default: // Unknown code
                meta.logger.warn(`TuYa_cover_control: Unhandled DP #${dp} for ${meta.device.manufacturerName}:
                ${JSON.stringify(dpValue)}`);
            }
        },
    },
};

const definition = {
    fingerprint: [
        {modelID: 'TS0601', manufacturerName: '_TZE200_68nvbio9'},
        {modelID: 'TS0601', manufacturerName: '_TZE200_cf1sl3tj'},
    ],
    model: 'TS0601_cover',
    vendor: 'TuYa',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fzLocal.device_specific],
    toZigbee: [tz.tuya_cover_control, tz.tuya_cover_options],
    exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.battery()],
    meta: {
        configureKey: 1,
        battery: {dontDividePercentage: true}
    },
};

module.exports = definition;

With that in mind, as @Oliviakrkk pointed out there are several more potential cases which this converter could handle

Current curtain position: code 3 Motor direction: code 5 Border: 16 Control 1 Curtain position setting 2 Best position 19 Mode 4 Click control 20 Motor direction 5 Work state 7 Situation set 17 Fult 12 Battery 13

nchieng Will this info suffice?

I'm just not entirely sure how to convert these without sinking a whole bunch of time into it unfortunately

@simonwood0609
Copy link

you're right @Hamper, I'd missed that line when copying+pasting. @gbeleris try this:

I've been trying to get my TZE200_pw7mji0l (0x84ba20fffe3a2afb) connected for days without success.

Zigbee2MQTT is running on a separate physical machine (Raspberry Pi) than Hass, currently have over 20 devices connected and have some experience with pairing so it should be straight forward but isn't for some reason.

Constantly seeing the same Unhandled DP #13 error, but more problematic I cannot get the device to appear in Home Assistant (and I do not know why). Could somebody help a dummy by providing the actual list of steps?

  1. I can see the device in Zigbee2MQTT - when I press buttons on the blind motor it reports to Zigbee2MQTT.
  2. I cannot see the device in Home Assistant. I've ensured that my MQTT Broker has "Enabled newly added entities." switched on (and with this enabled in past, the newly paired device instantly appears).

Kinda stuck not sure what to check next.

@jumpinf00l
Copy link

jumpinf00l commented Jan 9, 2023

@simonwood0609 here's what I did:

  1. Copy the code from my post above to config/zigbee2mqtt/zigbee_blind_motor.js
  2. Duplicate line 63
  3. Replace the manufacturerName from your duplicated line with your specific blind motor's manufacturerName (ie 'TZE200_pw7mji0l')
    image
  4. Open config/zigbee2mqtt/configuration.yaml
  5. Locate or add the external_converters: section, then add the name (not path) of the .js file from step 1
    external_converters:
    - zigbee_blind_motor.js
  6. Reload Zigbee2MQTT add-on, and check in the log tab to make sure everything is happy

From @Oliviakrkk's post, we can see that DP #13 is the battery level but as I mentioned it's reported every time the blind does something and can vary quite wildly which I assume is due to the draw on the battery. The code above interprets DP #13 as the battery level which should reduce the number of 'unhandled' messages, though some do still appear (there are a few in @Oliviakrkk's post) such as when setting the top/bottom positions which would also be nice to be able to set over Zigbee rather than the RF remote. I'd prefer to poll the blind for battery rather than grab it as the blind is doing something, but honestly haven't touched this since my last post

@simonwood0609
Copy link

simonwood0609 commented Jan 10, 2023

@jumpinf00l thanks so much for the ELI5 - very helpful!

I ended up messing up my instance of Zigbee2MQTT somehow (probably trying to edit far too many js files) so I rmdir and started from scratch github clone. This time the blind paired instantly - I didn't even need to create the external_converter which is strange. I'm glad I can now control the blind via Zigbee2MQTT.

Unfortunately no new devices appearing in Home Assistant (in the MQTT: Mosquitto broker device/entity lists). I have "Enable newly added entities." checked, and I can't find the device anywhere to delete it (in hope it'll re-create properly).

On a side note I just installed my first Shelly devices, dimmers (WiFi) and the setup process was smooth and it's been a dream to use so far, very pleased.

EDIT: Fixed! It was either patience or restarting the Mosquitto Broker integration in Home Assistant.

@jumpinf00l
Copy link

@simonwood0609 glad to see you got it sorted. The good old ‘turn it off and on again’ saves the day

@ouchkilljoys
Copy link

@simonwood0609 here's what I did:

  1. Copy the code from my post above to config/zigbee2mqtt/zigbee_blind_motor.js
  2. Duplicate line 63
  3. Replace the manufacturerName from your duplicated line with your specific blind motor's manufacturerName (ie 'TZE200_pw7mji0l')
    image
  4. Open config/zigbee2mqtt/configuration.yaml
  5. Locate or add the external_converters: section, then add the name (not path) of the .js file from step 1
    external_converters:
    - zigbee_blind_motor.js
  6. Reload Zigbee2MQTT add-on, and check in the log tab to make sure everything is happy

From @Oliviakrkk's post, we can see that DP #13 is the battery level but as I mentioned it's reported every time the blind does something and can vary quite wildly which I assume is due to the draw on the battery. The code above interprets DP #13 as the battery level which should reduce the number of 'unhandled' messages, though some do still appear (there are a few in @Oliviakrkk's post) such as when setting the top/bottom positions which would also be nice to be able to set over Zigbee rather than the RF remote. I'd prefer to poll the blind for battery rather than grab it as the blind is doing something, but honestly haven't touched this since my last post

I always assumed that Z2M polled battery devices every so often, but I guess the challenge here is that the battery data needs to be discarded when the blind is in motion?

@Cjkeenan
Copy link

Cjkeenan commented Mar 15, 2023

Has anyone run into intermittent "connection" issues with this external converter? It never actually leaves the mesh or reports as offline, it just stops responding to commands though. It seems to work for a bit, then you give it some time and they stop responding to commands. After I tried “waking” them up by reading something simple like time from the dev console, they start responding again, but again after some time they stop again. But this entire time, they still have a recent "Last Seen" value, which is the oddest part.

I am wondering if it is something as simple as I need to setup a reporting method to keep it alive, but as I have never messed with external converters I do not know. Their LQI's also hover around 100 which doesn't seem great, but also not terrible.

@cloudbr34k84
Copy link

Has anyone got the instructions for this. Can't find any online?

@jumpinf00l
Copy link

Has anyone got the instructions for this. Can't find any online?

Sure thing, sorry the original got a bit crumpled

CCO_20230425_200028_0001.pdf

@cloudbr34k84
Copy link

Has anyone got the instructions for this. Can't find any online?

Sure thing, sorry the original got a bit crumpled

CCO_20230425_200028_0001.pdf

Legend! I can finally re add it lol

@ianfromnyc
Copy link

ianfromnyc commented Jun 3, 2023

Looks like there is official support for ZM85EL-2Z in 1.31.0 but not the _TZE200_nw1r9hp6 variant. Can this be added?

See #17610
Koenkk/zigbee-herdsman-converters@552854c

Koenkk added a commit to Koenkk/zigbee-herdsman-converters that referenced this issue Jun 3, 2023
@Koenkk
Copy link
Owner

Koenkk commented Jun 3, 2023

@ianfromnyc added!

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)

@esackbauer
Copy link

esackbauer commented Sep 5, 2023

Battery status is not working for _TZE200_nw1r9hp6 model. It is still showing the old battery status of 55% from the time when I used an external converter, and is not updating to the correct status.

I used an external converter which I tweaked from another device which properly showed the battery status.
Please someone have a look at it, this was my external converter:

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 e = exposes.presets;
const ea = exposes.access;
const tuya = require('zigbee-herdsman-converters/lib/tuya');
const utils = require('zigbee-herdsman-converters/lib/utils');
const globalStore = require('zigbee-herdsman-converters/lib/store');

const fzLocal = {
    device_specific: {
        cluster: 'manuSpecificTuya',
        type: ['commandDataReport', 'commandDataResponse'],
        options: [exposes.options.invert_cover()],
        convert: (model, msg, publish, options, meta) => {

            const dpValue = tuya.firstDpValue(msg, meta, 'tuya_cover');
            const dp = dpValue.dp;
            const value = tuya.getDataValue(dpValue);

            switch (dp) {
            case tuya.dataPoints.coverPosition: // Started moving to position (triggered from Zigbee)
            case tuya.dataPoints.coverArrived: { // Arrived at position
                const invert = tuya.isCoverInverted(meta.device.manufacturerName) ? !options.invert_cover : options.invert_cover;
                const position = invert ? 100 - (value & 0xFF) : (value & 0xFF);
                const running = dp !== tuya.dataPoints.coverArrived;
                // Not all covers report coverArrived, so set running to false if device doesn't report position for a few seconds
                clearTimeout(globalStore.getValue(msg.endpoint, 'running_timer'));
                if (running) {
                    const timer = setTimeout(() => publish({running: false}), 3 * 1000);
                    globalStore.putValue(msg.endpoint, 'running_timer', timer);
                }
                if (position > 0 && position <= 100) {
                    return {running, position, state: 'OPEN'};
                } else if (position == 0) { // Report fully closed
                    return {running, position, state: 'CLOSE'};
                } else {
                    return {running}; // Not calibrated yet, no position is available
                }
            }
            case tuya.dataPoints.coverSpeed: // Cover is reporting its current speed setting
                return {motor_speed: value};
            case tuya.dataPoints.state:
            case tuya.dataPoints.coverChange: // Ignore manual cover change, it's not reliable between different covers!
                break;
            case tuya.dataPoints.config: // Returned by configuration set; ignore
                break;
            case 13: // Battery
                return {battery: value};
            default: // Unknown code
                meta.logger.warn(`TuYa_cover_control: Unhandled DP #${dp} for ${meta.device.manufacturerName}:
                ${JSON.stringify(dpValue)}`);
            }
        },
    },
};

const definition = {
    fingerprint: [
        {modelID: 'TS0601', manufacturerName: '_TZE200_68nvbio9'},
        {modelID: 'TS0601', manufacturerName: '_TZE200_nw1r9hp6'},
    ],
    model: 'TS0601_cover',
    vendor: 'TuYa',
    description: 'Zigbee/RF curtain motor',
    fromZigbee: [fzLocal.device_specific],
    toZigbee: [tz.tuya_cover_control, tz.tuya_cover_options],
    exposes: [e.cover_position().setAccess('position', ea.STATE_SET), e.battery()],
    meta: {
        configureKey: 1,
        battery: {dontDividePercentage: true}
    },
};

module.exports = definition;

@bat86
Copy link

bat86 commented Nov 22, 2023

I'm experiencing an issue with the 'Invert Cover' setting for a Zigbee curtain controller (Zemismart model ZM85EL-2Z (Zigbee Model - TS0601. Zigbee Manufacturer- _TZE200_cf1sl3tj)). Despite setting invert_cover to true, the open and close positions of the cover are not being inverted as expected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new device support New device support request stale Stale issues
Projects
None yet
Development

No branches or pull requests