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

Add support for listening to group addresses on Ember coordinator #695

Merged
merged 1 commit into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpChildrenCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpConfigurationCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpCountersCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpMulticastCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpPolicyCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpRoutingCommand;
import com.zsmartsystems.zigbee.console.ember.EmberConsoleNcpScanCommand;
Expand All @@ -51,13 +52,14 @@
public class EmberZigBeeConsoleCommandProvider implements ZigBeeConsoleCommandProvider {

@SuppressWarnings("null")
public static final List<ZigBeeConsoleCommand> EMBER_COMMANDS = unmodifiableList(asList(
new EmberConsoleMmoHashCommand(), new EmberConsoleNcpChildrenCommand(),
new EmberConsoleNcpConfigurationCommand(), new EmberConsoleNcpCountersCommand(),
new EmberConsoleNcpPolicyCommand(), new EmberConsoleNcpRoutingCommand(), new EmberConsoleNcpScanCommand(),
new EmberConsoleSecurityStateCommand(), new EmberConsoleNcpStateCommand(),
new EmberConsoleNcpTokenCommand(), new EmberConsoleTransientKeyCommand(), new EmberConsoleNcpValueCommand(),
new EmberConsoleNcpVersionCommand()));
public static final List<ZigBeeConsoleCommand> EMBER_COMMANDS = unmodifiableList(
asList(new EmberConsoleMmoHashCommand(), new EmberConsoleNcpChildrenCommand(),
new EmberConsoleNcpConfigurationCommand(), new EmberConsoleNcpCountersCommand(),
new EmberConsoleNcpMulticastCommand(), new EmberConsoleNcpPolicyCommand(),
new EmberConsoleNcpRoutingCommand(), new EmberConsoleNcpScanCommand(),
new EmberConsoleSecurityStateCommand(), new EmberConsoleNcpStateCommand(),
new EmberConsoleNcpTokenCommand(), new EmberConsoleTransientKeyCommand(),
new EmberConsoleNcpValueCommand(), new EmberConsoleNcpVersionCommand()));

private Map<String, ZigBeeConsoleCommand> emberCommands = EMBER_COMMANDS.stream()
.collect(toMap(ZigBeeConsoleCommand::getCommand, identity()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
*/
package org.openhab.binding.zigbee.ember.handler;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

Expand All @@ -22,6 +27,7 @@
import org.openhab.binding.zigbee.ember.EmberBindingConstants;
import org.openhab.binding.zigbee.ember.internal.EmberConfiguration;
import org.openhab.binding.zigbee.handler.ZigBeeCoordinatorHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.Bridge;
Expand All @@ -34,7 +40,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zsmartsystems.zigbee.dongle.ember.EmberNcp;
import com.zsmartsystems.zigbee.dongle.ember.ZigBeeDongleEzsp;
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EmberMulticastTableEntry;
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EmberStatus;
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspConfigId;
import com.zsmartsystems.zigbee.serialization.DefaultDeserializer;
import com.zsmartsystems.zigbee.serialization.DefaultSerializer;
Expand Down Expand Up @@ -64,6 +73,24 @@ public class EmberHandler extends ZigBeeCoordinatorHandler implements FirmwareUp
private static final String ASH_RX_NAK = "ASH_RX_NAK";
private static final String ASH_TX_NAK = "ASH_TX_NAK";

/**
* Sets the minimum size of the multicast address table
*/
private static final int GROUPS_MINIMUM = 3;

/**
* Sets the minimum headroom in the multicast table. This allows for room for the table to grow during a session.
* Note that the table size is set only on startup since this impacts the Ember memory use.
*/
private static final int GROUPS_HEADROOM = 3;

/**
* Sets the maximum size of the groups table. This is designed to be large enough to allow pretty much any
* configuration, but small enough that to protect against the user adding loads of random numbers or invalid
* formatting of the config string.
*/
private static final int GROUPS_MAXIMUM = 20;

private final Logger logger = LoggerFactory.getLogger(EmberHandler.class);

private final SerialPortManager serialPortManager;
Expand All @@ -89,6 +116,9 @@ protected void initializeDongle() {

@Override
protected void initializeDongleSpecific() {
EmberConfiguration config = getConfigAs(EmberConfiguration.class);
setGroupRegistration(config.zigbee_groupregistration);

if (pollingJob == null) {
Runnable pollingRunnable = new Runnable() {
@Override
Expand Down Expand Up @@ -132,6 +162,36 @@ public void run() {
}
}

@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
logger.debug("{}: Ember configuration received.", nodeIeeeAddress);

Map<String, Object> unhandledConfiguration = new HashMap<>();
Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
// Ignore any configuration parameters that have not changed
if (Objects.equals(configurationParameter.getValue(), configuration.get(configurationParameter.getKey()))) {
logger.debug("{}: Ember configuration update: Ignored {} as no change", nodeIeeeAddress,
configurationParameter.getKey());
continue;
}

logger.debug("{}: Ember configuration update: Processing {} -> {}", nodeIeeeAddress,
configurationParameter.getKey(), configurationParameter.getValue());

switch (configurationParameter.getKey()) {
case ZigBeeBindingConstants.CONFIGURATION_GROUPREGISTRATION:
setGroupRegistration((String) configurationParameter.getValue());
break;
default:
unhandledConfiguration.put(configurationParameter.getKey(), configurationParameter.getValue());
break;
}

configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
}
}

@Override
public void dispose() {
super.dispose();
Expand Down Expand Up @@ -296,4 +356,44 @@ private TransportConfig createTransportConfig(EmberConfiguration config) {
transportConfig.addOption(TransportConfigOption.CONCENTRATOR_CONFIG, concentratorConfig);
return transportConfig;
}

private void setGroupRegistration(String groupsString) {
if (groupsString.isBlank()) {
return;
}

logger.debug("ZigBee Ember Coordinator group registration is {}", groupsString);
ZigBeeDongleEzsp dongle = (ZigBeeDongleEzsp) zigbeeTransport;
EmberNcp ncp = dongle.getEmberNcp();

String[] groupsArray = groupsString.split(",");
Set<Integer> groups = new HashSet<>();

for (String groupString : groupsArray) {
groups.add(Integer.parseInt(groupString.trim(), 16));
}

int multicastTableSize = groups.size() + GROUPS_HEADROOM;
if (multicastTableSize < GROUPS_MINIMUM) {
multicastTableSize = GROUPS_MINIMUM;
} else if (multicastTableSize > GROUPS_MAXIMUM) {
multicastTableSize = GROUPS_MAXIMUM;
}
logger.debug("ZigBee Ember Coordinator multicast table size set to {}", multicastTableSize);
dongle.updateDefaultConfiguration(EzspConfigId.EZSP_CONFIG_MULTICAST_TABLE_SIZE, multicastTableSize);

int index = 0;
for (Integer group : groups) {
EmberMulticastTableEntry entry = new EmberMulticastTableEntry();
entry.setEndpoint(1);
entry.setNetworkIndex(0);
entry.setMulticastId(group);
EmberStatus result = ncp.setMulticastTableEntry(index, entry);

logger.debug("ZigBee Ember Coordinator multicast table index {} updated with {}, result {}", index, entry,
result);

index++;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ public class EmberConfiguration {
public Integer zigbee_childtimeout;
public Integer zigbee_concentrator;
public Integer zigbee_networksize;
public String zigbee_groupregistration;
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,16 @@
<advanced>true</advanced>
</parameter>

<parameter name="zigbee_installcode" type="text" groupName="network">
<label>Install Code</label>
<description>Add a temporary install key for device installation</description>
<advanced>true</advanced>
</parameter>
<parameter name="zigbee_installcode" type="text" groupName="network">
<label>Install Code</label>
<description>Add a temporary install key for device installation</description>
<advanced>true</advanced>
</parameter>

<parameter name="zigbee_groupregistration" type="text" groupName="network">
<label>Group Registrations</label>
<description>Registers the binding to receive group notifications. Groups are separated by a comma and must be defined in hexadecimal.</description>
</parameter>

<parameter name="zigbee_meshupdateperiod" type="integer" groupName="network">
<label>Mesh Update Period</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.openhab.binding.zigbee.ember;

import static org.junit.jupiter.api.Assertions.assertEquals;

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

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.openhab.binding.zigbee.ZigBeeBindingConstants;
import org.openhab.binding.zigbee.converter.ZigBeeChannelConverterFactory;
import org.openhab.binding.zigbee.ember.handler.EmberHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.thing.Bridge;

import com.zsmartsystems.zigbee.dongle.ember.EmberNcp;
import com.zsmartsystems.zigbee.dongle.ember.ZigBeeDongleEzsp;
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EmberMulticastTableEntry;
import com.zsmartsystems.zigbee.dongle.ember.ezsp.structure.EzspConfigId;

/**
*
* @author Chris Jackson
*
*/
public class EmberHandlerTest {
class EmberHandlerForTest extends EmberHandler {
public EmberHandlerForTest(Bridge coordinator, SerialPortManager serialPortManager,
ZigBeeChannelConverterFactory channelFactory, ZigBeeDongleEzsp dongle) {
super(coordinator, serialPortManager, channelFactory);
this.zigbeeTransport = dongle;
}

@Override
protected Configuration editConfiguration() {
return new Configuration();
}
}

@Test
public void setGroupRegistration() {
Bridge bridge = Mockito.mock(Bridge.class);
SerialPortManager serialPortManager = Mockito.mock(SerialPortManager.class);
ZigBeeChannelConverterFactory zigBeeChannelConverterFactory = Mockito.mock(ZigBeeChannelConverterFactory.class);

ZigBeeDongleEzsp dongle = Mockito.mock(ZigBeeDongleEzsp.class);
EmberNcp ncp = Mockito.mock(EmberNcp.class);
Mockito.when(dongle.getEmberNcp()).thenReturn(ncp);
EmberHandler handler = new EmberHandlerForTest(bridge, serialPortManager, zigBeeChannelConverterFactory,
dongle);

ArgumentCaptor<Integer> indexCaptor = ArgumentCaptor.forClass(Integer.class);
ArgumentCaptor<EmberMulticastTableEntry> valueCaptor = ArgumentCaptor.forClass(EmberMulticastTableEntry.class);

Map<String, Object> cfg = new HashMap<>();
cfg.put(ZigBeeBindingConstants.CONFIGURATION_GROUPREGISTRATION, "1234,5678, 90AB ,CDEF");
handler.handleConfigurationUpdate(cfg);

Mockito.verify(dongle).updateDefaultConfiguration(EzspConfigId.EZSP_CONFIG_MULTICAST_TABLE_SIZE, 7);
Mockito.verify(ncp, Mockito.times(4)).setMulticastTableEntry(indexCaptor.capture(), valueCaptor.capture());

int[] mcastId = { 0x1234, 0x5678, 0x90AB, 0xCDEF };
for (int cnt = 0; cnt < 4; cnt++) {
assertEquals(cnt, indexCaptor.getAllValues().get(cnt));
assertEquals(1, valueCaptor.getAllValues().get(cnt).getEndpoint());
assertEquals(0, valueCaptor.getAllValues().get(cnt).getNetworkIndex());
assertEquals(mcastId[cnt], valueCaptor.getAllValues().get(cnt).getMulticastId());
}
}
}
Loading