Skip to content

Commit

Permalink
Refactor Tuya into separate bundle
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Jackson <[email protected]> (+4 squashed commits)
Squashed commits:
[8c397c9] Add tests for ZigBeeConverterTuyaButton

Signed-off-by: Daniel Schall <[email protected]>
[d68a96a] Fix build break

Signed-off-by: Daniel Schall <[email protected]>
[9a37f0e] Add Tuya button support

Signed-off-by: Daniel Schall <[email protected]>
[af45e77] Create Tuya specific handler binding

Signed-off-by: Chris Jackson <[email protected]>
  • Loading branch information
cdjackson committed Sep 25, 2021
1 parent 0d9206e commit 8c2f358
Show file tree
Hide file tree
Showing 18 changed files with 967 additions and 6 deletions.
7 changes: 6 additions & 1 deletion bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<artifactId>org.openhab.addons.bom.zigbee</artifactId>
<packaging>pom</packaging>

<name>openHAB ZigBee :: BOM :: openHAB ZigBee Binding</name>
<name>openHAB Add-ons :: Bundles :: ZigBee Binding BOM</name>

<dependencies>
<dependency>
Expand Down Expand Up @@ -50,6 +50,11 @@
<artifactId>org.openhab.binding.zigbee.telegesis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.tuya</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.xbee</artifactId>
Expand Down
15 changes: 10 additions & 5 deletions feature/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,16 @@
<artifactId>org.openhab.binding.zigbee.ember</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.telegesis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.telegesis</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.tuya</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee.xbee</artifactId>
Expand Down
1 change: 1 addition & 0 deletions org.openhab.binding.zigbee.tuya/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/bin/
14 changes: 14 additions & 0 deletions org.openhab.binding.zigbee.tuya/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This content is produced and maintained by the openHAB project.

* Project home: https://www.openhab.org

== Declared Project Licenses

This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.

== Source Code

https://github.com/openhab/org.openhab.binding.zigbee

24 changes: 24 additions & 0 deletions org.openhab.binding.zigbee.tuya/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.zigbee.reactor</artifactId>
<version>3.2.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.zigbee.tuya</artifactId>

<name>openHAB Add-ons :: Bundles :: ZigBee Tuya Handler</name>

<dependencies>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.zigbee</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zigbee.tuya;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.type.ChannelTypeUID;

/**
* The {@link TuyaBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Chris Jackson - Initial contribution
*/
@NonNullByDefault
public class TuyaBindingConstants {

public static final String BINDING_ID = "zigbee";

public static final String CHANNEL_NAME_TUYA_BUTTON = "tuyabutton";
public static final String CHANNEL_LABEL_TUYA_BUTTON = "Button";
public static final ChannelTypeUID CHANNEL_TUYA_BUTTON = new ChannelTypeUID("zigbee:tuya_button");

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zigbee.tuya.converter;

import static java.lang.Integer.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.ExecutionException;

import org.openhab.core.thing.Channel;
import org.openhab.core.thing.CommonTriggerEvents;
import org.openhab.core.thing.ThingUID;
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
import org.openhab.binding.zigbee.handler.ZigBeeThingHandler;
import org.openhab.binding.zigbee.internal.converter.config.ZclReportingConfig;
import org.openhab.binding.zigbee.tuya.internal.TuyaButtonPressCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zsmartsystems.zigbee.CommandResult;
import com.zsmartsystems.zigbee.ZigBeeEndpoint;
import com.zsmartsystems.zigbee.zcl.ZclAttribute;
import com.zsmartsystems.zigbee.zcl.ZclAttributeListener;
import com.zsmartsystems.zigbee.zcl.ZclCluster;
import com.zsmartsystems.zigbee.zcl.ZclCommand;
import com.zsmartsystems.zigbee.zcl.ZclCommandListener;
import com.zsmartsystems.zigbee.zcl.ZclStatus;
import com.zsmartsystems.zigbee.zcl.clusters.ZclOnOffCluster;

/**
* Generic converter for Tuya buttons (e.g., Zemismart).
* <p>
* This converter is handling the Tuya-specific command (ID: 253) and emits button-pressed events.
* <p>
* As the configuration is done via channel properties, this converter is usable via static thing types only.
*
* @author Daniel Schall - initial contribution
*/
public class ZigBeeConverterTuyaButton extends ZigBeeBaseChannelConverter
implements ZclAttributeListener, ZclCommandListener {

private Logger logger = LoggerFactory.getLogger(ZigBeeConverterTuyaButton.class);

private ZclCluster clientCluster = null;
private ZclCluster serverCluster = null;

// Tuya devices sometimes send duplicate commands with the same tx id.
// We keep track of the last received Tx id and ignore the duplicate.
private Integer lastTxId = -1;

@Override
public Set<Integer> getImplementedClientClusters() {
return Collections.singleton(ZclOnOffCluster.CLUSTER_ID);
}

@Override
public Set<Integer> getImplementedServerClusters() {
return Collections.singleton(ZclOnOffCluster.CLUSTER_ID);
}

@Override
public boolean initializeDevice() {
ZclCluster clientCluster = endpoint.getOutputCluster(ZclOnOffCluster.CLUSTER_ID);
ZclCluster serverCluster = endpoint.getInputCluster(ZclOnOffCluster.CLUSTER_ID);

if (clientCluster == null) {
logger.error("{}: Error opening client cluster {} on endpoint {}", endpoint.getIeeeAddress(),
ZclOnOffCluster.CLUSTER_ID, endpoint.getEndpointId());
return false;
}

if (serverCluster == null) {
logger.error("{}: Error opening server cluster {} on endpoint {}", endpoint.getIeeeAddress(),
ZclOnOffCluster.CLUSTER_ID, endpoint.getEndpointId());
return false;
}

ZclReportingConfig reporting = new ZclReportingConfig(channel);

try {
CommandResult bindResponse = bind(serverCluster).get();
if (bindResponse.isSuccess()) {
// Configure reporting
ZclAttribute attribute = serverCluster.getAttribute(ZclOnOffCluster.ATTR_ONOFF);
CommandResult reportingResponse = attribute
.setReporting(reporting.getReportingTimeMin(), reporting.getReportingTimeMax()).get();
handleReportingResponse(reportingResponse, POLLING_PERIOD_HIGH, reporting.getPollingPeriod());
} else {
logger.debug("{}: Error 0x{} setting server binding", endpoint.getIeeeAddress(),
Integer.toHexString(bindResponse.getStatusCode()));
pollingPeriod = POLLING_PERIOD_HIGH;
}

} catch (InterruptedException | ExecutionException e) {
logger.error("{}: Exception setting reporting ", endpoint.getIeeeAddress(), e);
}

try {
CommandResult bindResponse = bind(clientCluster).get();
if (!bindResponse.isSuccess()) {
logger.error("{}: Error 0x{} setting client binding for cluster {}", endpoint.getIeeeAddress(),
toHexString(bindResponse.getStatusCode()), ZclOnOffCluster.CLUSTER_ID);
}
} catch (InterruptedException | ExecutionException e) {
logger.error("{}: Exception setting client binding to cluster {}: {}", endpoint.getIeeeAddress(),
ZclOnOffCluster.CLUSTER_ID, e);
}

return true;
}

@Override
public synchronized boolean initializeConverter(ZigBeeThingHandler thing) {
super.initializeConverter(thing);

clientCluster = endpoint.getOutputCluster(ZclOnOffCluster.CLUSTER_ID);
serverCluster = endpoint.getInputCluster(ZclOnOffCluster.CLUSTER_ID);

if (clientCluster == null) {
logger.error("{}: Error opening device client controls", endpoint.getIeeeAddress());
return false;
}

if (serverCluster == null) {
logger.error("{}: Error opening device server controls", endpoint.getIeeeAddress());
return false;
}

clientCluster.addCommandListener(this);
serverCluster.addAttributeListener(this);

// Add Tuya-specific command
//
HashMap<Integer, Class<? extends ZclCommand>> commandMap = new HashMap<>();
commandMap.put(TuyaButtonPressCommand.COMMAND_ID, TuyaButtonPressCommand.class);
clientCluster.addClientCommands(commandMap);

return true;
}

@Override
public void disposeConverter() {
if(clientCluster != null) {
clientCluster.removeCommandListener(this);
}
if (serverCluster != null) {
serverCluster.removeAttributeListener(this);
}
}

@Override
public void handleRefresh() {
// nothing to do, as we only listen to commands
}

@Override
public Channel getChannel(ThingUID thingUID, ZigBeeEndpoint endpoint) {
// This converter is used only for channels specified in static thing types, and cannot be used to construct
// channels based on an endpoint alone.
return null;
}

@Override
public boolean commandReceived(ZclCommand command) {
logger.debug("{} received command {}", endpoint.getIeeeAddress(), command);
Integer thisTxId = command.getTransactionId();
if(lastTxId == thisTxId)
{
logger.debug("{} ignoring duplicate command {}", endpoint.getIeeeAddress(), thisTxId);
}
else if (command instanceof TuyaButtonPressCommand) {
TuyaButtonPressCommand tuyaButtonPressCommand = (TuyaButtonPressCommand) command;
thing.triggerChannel(channel.getUID(), getEventType(tuyaButtonPressCommand.getPressType()));
clientCluster.sendDefaultResponse(command, ZclStatus.SUCCESS);
}
else {
logger.warn("{} received unknown command {}", endpoint.getIeeeAddress(), command);
}

lastTxId = thisTxId;
return true;
}

@Override
public void attributeUpdated(ZclAttribute attribute, Object val) {
logger.debug("{}: ZigBee attribute reports {}", endpoint.getIeeeAddress(), attribute);
}

private String getEventType(Integer pressType)
{
switch(pressType)
{
case 0:
return CommonTriggerEvents.SHORT_PRESSED;
case 1:
return CommonTriggerEvents.DOUBLE_PRESSED;
case 2:
return CommonTriggerEvents.LONG_PRESSED;
default:
logger.warn("{} received unknown pressType {}", endpoint.getIeeeAddress(), pressType);
return CommonTriggerEvents.SHORT_PRESSED;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2021 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.zigbee.tuya.converter;

import java.util.HashMap;
import java.util.Map;

import org.openhab.binding.zigbee.ZigBeeBindingConstants;
import org.openhab.binding.zigbee.converter.ZigBeeBaseChannelConverter;
import org.openhab.binding.zigbee.converter.ZigBeeChannelConverterFactory;
import org.openhab.binding.zigbee.converter.ZigBeeChannelConverterProvider;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Component;

/**
* The base {@link ZigBeeTuyaChannelConverterProvider} of the binding making the Tuya specific
* {@link ZigBeeBaseChannelConverter}s available for the {@link ZigBeeChannelConverterFactory}.
*
* @author Chris Jackson - Initial contribution
*/
@Component(immediate = true, service = ZigBeeChannelConverterProvider.class)
public final class ZigBeeTuyaChannelConverterProvider implements ZigBeeChannelConverterProvider {

private final Map<ChannelTypeUID, Class<? extends ZigBeeBaseChannelConverter>> channelMap = new HashMap<>();

public ZigBeeTuyaChannelConverterProvider() {
// Add all the converters into the map...
channelMap.put(ZigBeeBindingConstants.CHANNEL_COLOR_COLOR, ZigBeeConverterTuyaButton.class);
}

@Override
public Map<ChannelTypeUID, Class<? extends ZigBeeBaseChannelConverter>> getChannelConverters() {
return channelMap;
}
}
Loading

0 comments on commit 8c2f358

Please sign in to comment.