[External Converter]: TS0301 from _TZE200_cirjrpxe
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 15.7k
- Forks
- 2k
- Avg merge
- 18h 55m
- Merged PRs (30d)
- 35
Description
Link
Database entry
{"id":22,"type":"EndDevice","ieeeAddr":"0xe406bffffe5767d5","nwkAddr":31974,"manufId":4098,"manufName":"_TZE200_cirjrpxe","powerSource":"Battery","modelId":"TS0301","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":514,"inClusterList":[0,4,1,5,61184,3,258],"outClusterList":[25,10],"clusters":{"genBasic":{"attributes":{"65503":"�<�1\u0011","65506":31,"65508":0,"65534":0,"modelId":"TS0301","manufacturerName":"_TZE200_cirjrpxe","stackVersion":0,"dateCode":"","zclVersion":3,"appVersion":73,"powerSource":3}},"closuresWindowCovering":{"attributes":{"currentPositionLiftPercentage":9,"currentPositionTiltPercentage":0,"configStatus":43,"windowCoveringType":0}},"genPowerCfg":{"attributes":{"batteryPercentageRemaining":200}}},"binds":[{"cluster":258,"type":"endpoint","deviceIeeeAddress":"0x00124b0021600410","endpointID":1}],"configuredReportings":[{"cluster":258,"attrId":9,"minRepIntval":1,"maxRepIntval":65000,"repChange":1},{"cluster":258,"attrId":8,"minRepIntval":1,"maxRepIntval":65000,"repChange":1}],"meta":{}}},"appVersion":73,"stackVersion":0,"hwVersion":1,"dateCode":"","zclVersion":3,"interviewCompleted":true,"interviewState":"SUCCESSFUL","meta":{"configured":"0.0.0"},"lastSeen":1784558639101}
Zigbee2MQTT version
2.12.1 (aa909a8a62f76e2dd98ace3a172bca88ee56f5fe
)
External converter
import * as exposes from "zigbee-herdsman-converters/lib/exposes";
import * as m from "zigbee-herdsman-converters/lib/modernExtend";
import * as tuya from "zigbee-herdsman-converters/lib/tuya";
const {tuyaMagicPacket, dpBinary, dpNumeric, dpEnumLookup, dpBattery, dpAction, combineActions} = tuya.modernExtend;
const e = exposes.presets;
const ea = exposes.access;
function dpBitmapLookup(args) {
const {name, dp, type, lookup, description, endpoint, expose, skip} = args;
let exp;
if (expose) {
exp = expose;
} else {
exp = new exposes.List(name, ea.STATE, new exposes.Enum(name, ea.STATE, Object.keys(lookup)).withDescription(description));
}
if (endpoint) exp = exp.withEndpoint(endpoint);
const handlers = tuya.getHandlersForDP(
name,
dp,
type,
{
from: (value) => {
const faults = [];
for (const key in lookup) {
const bit = 1 << lookup[key];
if ((value & bit) === bit) {
faults.push(key);
}
}
return faults;
},
},
true,
skip,
endpoint,
);
return {exposes: [exp], fromZigbee: handlers[0], toZigbee: handlers[1], isModernExtend: true};
}
function dpButton(args) {
const {name, description, press, value, expose} = args;
let exp;
if (expose) {
exp = expose;
} else {
exp = new exposes.Enum(name, ea.SET, [press]).withDescription(description);
}
const ext = dpEnumLookup({lookup: {[press]: value}, expose: exp, ...args});
delete ext.fromZigbee;
return ext;
}
export default {
fingerprint: tuya.fingerprint("TS0301", ["_TZE200_cirjrpxe"]),
model: "ZM15B",
vendor: "Zemismart",
description: "Tubular motor",
extend: [
tuyaMagicPacket(),
m.windowCovering({controls: ["lift"], coverInverted: true}),
dpEnumLookup({
dp: 110,
name: "click_control",
type: tuya.dataTypes.enum,
lookup: {up: 0, down: 1},
expose: e.enum("click_control", ea.SET, ["up", "down"]).withDescription("Step control (up/down)"),
}),
dpButton({
dp: 101,
name: "favorite_position",
description: "Go to favorite position",
type: tuya.dataTypes.enum,
press: "GO",
value: 0,
}),
dpBinary({
dp: 7,
name: "travel_direction",
description: "Current direction of travel",
type: tuya.dataTypes.enum,
valueOn: ["opening", 0],
valueOff: ["closing", 1],
readOnly: true,
}),
dpBattery({dp: 103}),
dpEnumLookup({
dp: 124,
name: "battery_charge_status",
type: tuya.dataTypes.enum,
lookup: {Unknown: 0, Charging: 1, "Fully charged": 2, "Not charging": 3},
expose: e
.enum("battery_charge_status", ea.STATE, ["Unknown", "Charging", "Fully charged", "Not charging"])
.withDescription("Battery charging status")
.withCategory("diagnostic"),
}),
dpNumeric({
dp: 10,
name: "opening_time",
type: tuya.dataTypes.number,
expose: e
.numeric("opening_time", ea.STATE)
.withUnit("ms")
.withValueMin(0)
.withValueMax(240000)
.withDescription("Time taken to move from fully closed to fully open")
.withCategory("diagnostic"),
}),
dpNumeric({
dp: 116,
name: "closing_time",
type: tuya.dataTypes.number,
expose: e
.numeric("closing_time", ea.STATE)
.withUnit("ms")
.withValueMin(0)
.withValueMax(240000)
.withDescription("Time taken to move from fully open to fully closed")
.withCategory("diagnostic"),
}),
dpBinary({
dp: 5,
name: "reverse_direction",
type: tuya.dataTypes.enum,
valueOn: ["forward", 0],
valueOff: ["back", 1],
expose: e.binary("reverse_direction", ea.STATE_SET, "forward", "back").withDescription("Direction the motor runs").withCategory("config"),
}),
dpButton({
dp: 102,
name: "remote_pairing",
type: tuya.dataTypes.bool,
press: "ENABLE",
value: true,
expose: e.enum("remote_pairing", ea.SET, ["ENABLE"]).withDescription("Enable pairing with a remote").withCategory("config"),
}),
dpEnumLookup({
dp: 107,
name: "limit_state",
readOnly: true,
type: tuya.dataTypes.enum,
lookup: {"Limits not set": 0, "Upper and lower limits set": 1, "Upper and lower limits set, and favorite position": 2},
expose: e
.enum("limit_state", ea.STATE, ["Limits not set", "Upper and lower limits set", "Upper and lower limits set, and favorite position"])
.withDescription("Limit setting status")
.withCategory("diagnostic"),
}),
dpButton({
dp: 118,
name: "clear_upper_limit",
type: tuya.dataTypes.enum,
press: "CLEAR",
value: 0,
expose: e.enum("clear_upper_limit", ea.SET, ["CLEAR"]).withDescription("Clear the upper limit position").withCategory("config"),
}),
dpButton({
dp: 118,
name: "clear_lower_limit",
type: tuya.dataTypes.enum,
press: "CLEAR",
value: 1,
expose: e.enum("clear_lower_limit", ea.SET, ["CLEAR"]).withDescription("Clear the lower limit position").withCategory("config"),
}),
dpButton({
dp: 118,
name: "clear_favorite_position",
type: tuya.dataTypes.enum,
press: "CLEAR",
value: 2,
expose: e.enum("clear_favorite_position", ea.SET, ["CLEAR"]).withDescription("Clear the favorite position").withCategory("config"),
}),
dpButton({
dp: 118,
name: "set_upper_limit",
type: tuya.dataTypes.enum,
press: "SET",
value: 3,
expose: e
.enum("set_upper_limit", ea.SET, ["SET"])
.withDescription("Set the upper limit position to the current position")
.withCategory("config"),
}),
dpButton({
dp: 118,
name: "set_lower_limit",
type: tuya.dataTypes.enum,
press: "SET",
value: 4,
expose: e
.enum("set_lower_limit", ea.SET, ["SET"])
.withDescription("Set the lower limit position to the current position")
.withCategory("config"),
}),
dpButton({
dp: 118,
name: "set_favorite_position",
type: tuya.dataTypes.enum,
press: "SET",
value: 5,
expose: e
.enum("set_favorite_position", ea.SET, ["SET"])
.withDescription("Set the favorite position to the current position")
.withCategory("config"),
}),
combineActions([
dpAction({
dp: 105,
lookup: {
"Remote paired successfully": 0,
"Remote unpaired successfully": 1,
"Pairing timeout": 2,
},
}),
dpAction({
dp: 119,
lookup: {
"Limit deleted successfully": 0,
"Limit deletion failure": 1,
"Limit added successfully": 2,
"Limit addition failure": 3,
},
}),
]),
dpBitmapLookup({
dp: 12,
name: "faults",
type: tuya.dataTypes.bitmap,
lookup: {
"Motor fault": 0,
"Limit fault": 1,
"Limits not set": 2,
"Favorite position not set": 3,
"Automatic limit setting fault": 4,
"Low voltage fault": 5,
},
expose: e
.list(
"faults",
ea.STATE,
e.enum("fault", ea.STATE, [
"Motor fault",
"Limit fault",
"Limits not set",
"Favorite position not set",
"Automatic limit setting fault",
"Low voltage fault",
]),
)
.withDescription("Fault status")
.withCategory("diagnostic"),
}),
],
};
What does/doesn't work with the external definition?
I think this is pretty complete.
Working:
- Basic cover control - opening, closing, going to specific position, stopping, position reporting
- "click" adjustment up and down to move small amounts at a time
- Setting and clearing upper/lower limits and favorite position
- Moving to favorite position
- Battery level and charging status indication
Implemented but untested:
- Remote pairing/unpairing - I don't have a remote to test this with
- Fault status reporting
Not working/implemented:
- A number of DPs aren't implemented as I couldn't get them to do anything useful (DP 11 situation_set, DP 104 battery_switch, DP 106 fav_pos_switch, DP 120 limit_set_switch)
- DP 129 poweroff_reset which I assume does a factory reset I haven't tried
I was imagining the dpBitmapLookup and dpButton functions would be added to tuya.modernExtends
Notes
software_build_id: undefined
date_code: ``
endpoints:
{"1":{"clusters":{"input":["genBasic","genGroups","genPowerCfg","genScenes","manuSpecificTuya","genIdentify","closuresWindowCovering"],"output":["genOta","genTime"]}}}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the supplied external converter and the repository's modernExtend and tuya APIs. Verify the listed working behaviors, then assess the untested remote-pairing and fault-reporting paths separately. Done means the converter is integrated with the project conventions and the documented supported controls work without relying on the external definition.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, typescript
- Domain
- embedded-iot
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100