added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.cbus-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-cbus" description="CBus Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.cbus/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2020 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.cbus;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link CBusBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusBindingConstants {
public static final String BINDING_ID = "cbus";
// List of main things
public static final String BRIDGE_CGATE = "cgate";
public static final String BRIDGE_NETWORK = "network";
public static final String THING_GROUP = "group";
public static final String THING_LIGHT = "light";
public static final String THING_TEMPERATURE = "temperature";
public static final String THING_TRIGGER = "trigger";
public static final String THING_DALI = "dali";
// List of all Thing Type UIDs
public static final ThingTypeUID BRIDGE_TYPE_CGATE = new ThingTypeUID(BINDING_ID, BRIDGE_CGATE);
public static final ThingTypeUID BRIDGE_TYPE_NETWORK = new ThingTypeUID(BINDING_ID, BRIDGE_NETWORK);
public static final ThingTypeUID THING_TYPE_GROUP = new ThingTypeUID(BINDING_ID, THING_GROUP);
public static final ThingTypeUID THING_TYPE_LIGHT = new ThingTypeUID(BINDING_ID, THING_LIGHT);
public static final ThingTypeUID THING_TYPE_TEMPERATURE = new ThingTypeUID(BINDING_ID, THING_TEMPERATURE);
public static final ThingTypeUID THING_TYPE_TRIGGER = new ThingTypeUID(BINDING_ID, THING_TRIGGER);
public static final ThingTypeUID THING_TYPE_DALI = new ThingTypeUID(BINDING_ID, THING_DALI);
// List of all Channel ids
public static final String CHANNEL_LEVEL = "level";
public static final String CHANNEL_STATE = "state";
public static final String CHANNEL_TEMP = "temp";
public static final String CHANNEL_VALUE = "value";
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<ThingTypeUID>(
Arrays.asList(BRIDGE_TYPE_CGATE, BRIDGE_TYPE_NETWORK, THING_TYPE_GROUP, THING_TYPE_LIGHT,
THING_TYPE_TEMPERATURE, THING_TYPE_TRIGGER, THING_TYPE_DALI));
public static final Set<ThingTypeUID> NETWORK_DISCOVERY_THING_TYPES_UIDS = new HashSet<ThingTypeUID>(
Arrays.asList(BRIDGE_TYPE_NETWORK));
public static final String CONFIG_NETWORK_ID = "id";
public static final String CONFIG_NETWORK_PROJECT = "project";
public static final String CONFIG_NETWORK_SYNC = "syncInterval";
public static final String PROPERTY_NETWORK_NAME = "name";
public static final String CONFIG_CGATE_IP_ADDRESS = "ipAddress";
public static final String CONFIG_GROUP_ID = "group";
public static final String PROPERTY_NETWORK_ID = "CBUS Network Id";
public static final String PROPERTY_APPLICATION_ID = "CBUS Application Id";
public static final String PROPERTY_GROUP_NAME = "CBUS Group Name";
public static final int CBUS_APPLICATION_LIGHTING = 56;
public static final int CBUS_APPLICATION_TEMPERATURE = 25;
public static final int CBUS_APPLICATION_TRIGGER = 202;
public static final int CBUS_APPLICATION_DALI = 95;
}

View File

@@ -0,0 +1,379 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.binding.cbus.internal.CBusCGateConfiguration;
import org.openhab.binding.cbus.internal.CBusThreadPool;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateConnectException;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.CGateInterface;
import com.daveoxley.cbus.CGateSession;
import com.daveoxley.cbus.events.EventCallback;
import com.daveoxley.cbus.status.StatusChangeCallback;
/**
* The {@link CBusCGateHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusCGateHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(CBusCGateHandler.class);
private @Nullable InetAddress ipAddress;
public @Nullable CGateSession cGateSession;
private @Nullable ScheduledFuture<?> keepAliveFuture;
public CBusCGateHandler(Bridge br) {
super(br);
}
// This is abstract in base class so have to implement it.
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public void initialize() {
updateStatus(ThingStatus.OFFLINE);
logger.debug("Initializing CGate Bridge handler. {} {}", getThing().getThingTypeUID(), getThing().getUID());
CBusCGateConfiguration configuration = getConfigAs(CBusCGateConfiguration.class);
logger.debug("Using configuration {}", configuration);
try {
this.ipAddress = InetAddress.getByName(configuration.ipAddress);
} catch (UnknownHostException e1) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"IP Address not resolvable");
return;
}
InetAddress address = this.ipAddress;
if (address != null) {
logger.debug("CGate IP {}.", address.getHostAddress());
}
keepAliveFuture = scheduler.scheduleWithFixedDelay(this::keepAlive, 0, 100, TimeUnit.SECONDS);
}
private void keepAlive() {
CGateSession session = cGateSession;
if (session == null || !session.isConnected()) {
if (!getThing().getStatus().equals(ThingStatus.ONLINE)) {
connect();
} else {
updateStatus();
}
}
}
private void connect() {
CGateSession cGateSession = this.cGateSession;
if (cGateSession == null) {
cGateSession = CGateInterface.connect(this.ipAddress, 20023, 20024, 20025, new CBusThreadPool());
cGateSession.registerEventCallback(new EventMonitor());
cGateSession.registerStatusChangeCallback(new StatusChangeMonitor());
this.cGateSession = cGateSession;
}
if (cGateSession.isConnected()) {
logger.debug("CGate session reports online");
updateStatus(ThingStatus.ONLINE);
} else {
try {
cGateSession.connect();
updateStatus();
} catch (CGateConnectException e) {
updateStatus();
logger.debug("Failed to connect to CGate:", e);
try {
cGateSession.close();
} catch (CGateException ignore) {
// We dont really care if an exception is thrown when clossing the connection after a failure
// connecting.
}
}
}
}
private void updateStatus() {
ThingStatus lastStatus = getThing().getStatus();
CGateSession cGateSession = this.cGateSession;
if (cGateSession == null) {
return;
}
if (cGateSession.isConnected()) {
updateStatus(ThingStatus.ONLINE);
} else {
if (lastStatus != ThingStatus.OFFLINE) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
if (!getThing().getStatus().equals(lastStatus)) {
boolean isOnline = getThing().getStatus().equals(ThingStatus.ONLINE);
updateChildThings(isOnline);
}
}
private void updateChildThings(boolean isOnline) {
scheduler.execute(() -> {
// now also re-initialize all network handlers
for (Thing thing : getThing().getThings()) {
ThingHandler handler = thing.getHandler();
if (handler instanceof CBusNetworkHandler) {
((CBusNetworkHandler) handler).cgateStateChanged(isOnline);
}
}
});
}
private void updateGroup(@Nullable String address, @Nullable String value) {
if (address == null || value == null) {
return;
}
logger.debug("updateGroup address {}", address);
// Address should be of the form //Project/network/application/group
if (!address.startsWith("//")) {
logger.debug("Address does not start with // so ignoring this update");
return;
}
String[] addressParts = address.substring(2).split("/");
if (addressParts.length != 4) {
logger.debug("Address is badly formed so ignoring this update length of parts is {} not 4",
addressParts.length);
return;
}
updateGroup(Integer.parseInt(addressParts[1]), Integer.parseInt(addressParts[2]),
Integer.parseInt(addressParts[3]), value);
}
private void updateGroup(int network, int application, int group, String value) {
for (Thing networkThing : getThing().getThings()) {
// Is this networkThing from the network we are looking for...
if (networkThing.getThingTypeUID().equals(CBusBindingConstants.BRIDGE_TYPE_NETWORK)) {
CBusNetworkHandler netThingHandler = (CBusNetworkHandler) networkThing.getHandler();
if (netThingHandler == null || netThingHandler.getNetworkId() != network) {
continue;
}
// Loop through all the things on this network and see if they match the application / group
for (Thing thing : netThingHandler.getThing().getThings()) {
ThingHandler thingThingHandler = thing.getHandler();
if (thingThingHandler == null) {
continue;
}
if (thingThingHandler instanceof CBusGroupHandler) {
((CBusGroupHandler) thingThingHandler).updateGroup(application, group, value);
}
}
}
}
}
public @Nullable CGateSession getCGateSession() {
return cGateSession;
}
@Override
public void dispose() {
ScheduledFuture<?> keepAliveFuture = this.keepAliveFuture;
if (keepAliveFuture != null) {
keepAliveFuture.cancel(true);
}
CGateSession cGateSession = this.cGateSession;
if (cGateSession != null && cGateSession.isConnected()) {
try {
cGateSession.close();
} catch (CGateException e) {
logger.warn("Cannot close CGate session", e);
}
} else {
logger.debug("no session or it is disconnected");
}
super.dispose();
}
@NonNullByDefault
private class EventMonitor extends EventCallback {
@Override
public boolean acceptEvent(int eventCode) {
return true;
}
@Override
public void processEvent(@Nullable CGateSession cgate_session, int eventCode,
@Nullable GregorianCalendar event_time, @Nullable String event) {
if (event == null) {
return;
}
if (eventCode == 701) {
// By Marking this as Nullable it fools the static analyser into understanding that poll can return Null
LinkedList<@Nullable String> tokenizer = new LinkedList<>(Arrays.asList(event.trim().split("\\s+")));
@Nullable
String address = tokenizer.poll();
tokenizer.poll();
@Nullable
String value = tokenizer.poll();
if (value != null && value.startsWith("level=")) {
String level = value.replace("level=", "");
updateGroup(address, level);
}
}
}
}
@NonNullByDefault
private class StatusChangeMonitor extends StatusChangeCallback {
@Override
public boolean isActive() {
return true;
}
@Override
public void processStatusChange(@Nullable CGateSession cGateSession, @Nullable String status) {
if (cGateSession == null || status == null) {
return;
}
if (status.startsWith("# ")) {
status = status.substring(2);
// Shouldnt need to check for null but this silences a warning
if (status == null || status.isEmpty()) {
return;
}
}
logger.debug("ProcessStatusChange {}", status);
String[] contents = status.split("#");
if (cGateSession.getSessionID() != null
&& contents[1].contains("sessionId=" + cGateSession.getSessionID())) {
// We created this event - don't worry about processing it again...
return;
}
// By Marking this as Nullable it fools the static analyser into understanding that poll can return Null
LinkedList<@Nullable String> tokenizer = new LinkedList<>(Arrays.asList(contents[0].split("\\s+")));
@Nullable
String firstToken = tokenizer.poll();
if (firstToken == null) {
logger.debug("ProcessStateChange: Cant tokenize status {}", status);
return;
}
switch (firstToken) {
case "lighting": {
@Nullable
String state = tokenizer.poll();
@Nullable
String address = tokenizer.poll();
if ("ramp".equals(state)) {
state = tokenizer.poll();
}
updateGroup(address, state);
break;
}
case "temperature": {
// For temperature we ignore the state
tokenizer.poll();
@Nullable
String address = tokenizer.poll();
@Nullable
String temp = tokenizer.poll();
updateGroup(address, temp);
break;
}
case "trigger": {
@Nullable
String command = tokenizer.poll();
@Nullable
String address = tokenizer.poll();
if ("event".equals(command)) {
@Nullable
String level = tokenizer.poll();
updateGroup(address, level);
} else if ("indicatorkill".equals(command)) {
updateGroup(address, "-1");
} else {
logger.warn("Unhandled trigger command {} - status {}", command, status);
}
break;
}
case "clock": {
@Nullable
String address = "";
@Nullable
String value = "";
@Nullable
String type = tokenizer.poll();
if ("date".equals(type)) {
address = tokenizer.poll() + "/1";
value = tokenizer.poll();
} else if ("time".equals(type)) {
address = tokenizer.poll() + "/0";
value = tokenizer.poll();
} else if (!"request_refresh".equals(type)) {
// We dont handle request_refresh as we are not a clock master
logger.debug("Received unknown clock event: {}", status);
}
if (value != null && !value.isEmpty()) {
updateGroup(address, value);
}
break;
}
default: {
LinkedList<String> commentTokenizer = new LinkedList<>(Arrays.asList(contents[1].split("\\s+")));
if ("lighting".equals(commentTokenizer.peek())) {
commentTokenizer.poll();
@Nullable
String commentToken = commentTokenizer.peek();
if ("SyncUpdate".equals(commentToken)) {
commentTokenizer.poll();
@Nullable
String address = commentTokenizer.poll();
@Nullable
String level = commentTokenizer.poll();
level = level.replace("level=", "");
updateGroup(address, level);
}
} else {
logger.debug("Received unparsed event: '{}'", status);
}
break;
}
}
}
}
}

View File

@@ -0,0 +1,126 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
/**
* The {@link CBusDaliHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusDaliHandler extends CBusGroupHandler {
private final Logger logger = LoggerFactory.getLogger(CBusDaliHandler.class);
public CBusDaliHandler(Thing thing) {
super(thing, CBusBindingConstants.CBUS_APPLICATION_DALI);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Group group = this.group;
if (group == null) {
return;
}
if (command instanceof RefreshType) {
try {
int level = group.getLevel();
logger.debug("handle RefreshType Command for Chanell {} Group {} level {}", channelUID, groupId, level);
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_LEVEL)) {
updateState(channelUID, new PercentType((int) (level * 100 / 255.0)));
}
} catch (CGateException e) {
logger.debug("Failed to getLevel for group {}", groupId, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
} else {
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_LEVEL)) {
logger.debug("Channel Level command for {}: {}", channelUID, command);
try {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
group.on();
} else if (command == OnOffType.OFF) {
group.off();
}
} else if (command instanceof PercentType) {
PercentType value = (PercentType) command;
group.ramp((int) Math.round(value.doubleValue() / 100 * 255), 0);
} else if (command instanceof IncreaseDecreaseType) {
int level = group.getLevel();
if (command == IncreaseDecreaseType.DECREASE) {
level = Math.max(level - 1, 0);
} else if (command == IncreaseDecreaseType.INCREASE) {
level = Math.min(level + 1, 255);
}
group.ramp(level, 0);
logger.debug("Change group level to {}", level);
}
} catch (CGateException e) {
logger.debug("Cannot send command {} to {}", command, group, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
}
}
}
public void updateGroup(int updateApplicationId, int updateGroupId, String value) {
if (updateGroupId == groupId && updateApplicationId == applicationId) {
Thing thing = getThing();
Channel channel = thing.getChannel(CBusBindingConstants.CHANNEL_LEVEL);
if (channel != null) {
ChannelUID channelUID = channel.getUID();
if ("on".equalsIgnoreCase(value) || "255".equalsIgnoreCase(value)) {
updateState(channelUID, OnOffType.ON);
updateState(channelUID, new PercentType(100));
} else if ("off".equalsIgnoreCase(value) || "0".equalsIgnoreCase(value)) {
updateState(channelUID, OnOffType.OFF);
updateState(channelUID, new PercentType(0));
} else {
try {
int v = Integer.parseInt(value);
PercentType perc = new PercentType(Math.round(v * 100 / 255));
updateState(channelUID, perc);
} catch (NumberFormatException e) {
logger.warn(
"Invalid value presented to channel {}. Received {}, expected On/Off or decimal value",
channelUID, value);
}
}
logger.debug("Updating CBus Lighting Group {} with value {}", thing.getUID(), value);
} else {
logger.debug("Failed to Updat CBus Lighting Group {} with value {}: No Channel", thing.getUID(), value);
}
}
}
}

View File

@@ -0,0 +1,160 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.binding.cbus.internal.CBusGroupConfiguration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.Application;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
import com.daveoxley.cbus.Network;
/**
* The {@link CBusGroupHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public abstract class CBusGroupHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(CBusGroupHandler.class);
protected @Nullable CBusNetworkHandler cBusNetworkHandler;
protected @Nullable Group group;
protected int applicationId = -1;
protected int groupId = -1;
public CBusGroupHandler(Thing thing, int applicationId) {
super(thing);
this.applicationId = applicationId;
}
@Override
public abstract void handleCommand(ChannelUID channelUID, Command command);
@Override
public void initialize() {
/*
* Cast to Nullable in map to avoid compiler warnings
*/
CBusGroupConfiguration configuration = getConfigAs(CBusGroupConfiguration.class);
logger.debug("Using configuration {}", configuration);
groupId = configuration.group;
CBusNetworkHandler cBusNetworkHandler = getCBusNetworkHandler();
this.cBusNetworkHandler = cBusNetworkHandler;
if (cBusNetworkHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No CBusNetworkHandler Bridge available");
return;
}
updateStatus();
Map<String, String> updatedProperties = editProperties();
updatedProperties.put(CBusBindingConstants.PROPERTY_APPLICATION_ID, Integer.toString(applicationId));
updatedProperties.put(CBusBindingConstants.PROPERTY_NETWORK_ID,
Integer.toString(cBusNetworkHandler.getNetworkId()));
updateProperties(updatedProperties);
}
public void updateStatus() {
try {
logger.debug("updateStatus UID: {} applicaton: {} group: {}", getThing().getUID(), applicationId, groupId);
CBusNetworkHandler networkHandler = cBusNetworkHandler;
if (networkHandler == null || !networkHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} else {
Group group = this.group;
if (group == null) {
group = getGroup();
this.group = group;
}
if (group == null) {
logger.debug("Set state to configuration error -no group");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"No Group object available");
} else if (group.getNetwork().isOnline()) {
updateStatus(ThingStatus.ONLINE);
try {
Map<String, String> updatedProperties = editProperties();
updatedProperties.put(CBusBindingConstants.PROPERTY_GROUP_NAME, group.getName());
updateProperties(updatedProperties);
} catch (CGateException ignore) {
// Cant get name so properties wont be updated
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Network is not reporting online");
}
}
} catch (CGateException e) {
logger.debug("Problem checking network state for network {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
}
public abstract void updateGroup(int application, int group, String value);
private @Nullable Group getGroup() {
try {
CBusNetworkHandler networkHandler = cBusNetworkHandler;
if (networkHandler == null) {
return null;
}
Network network = networkHandler.getNetwork();
if (network != null) {
Application application = network.getApplication(applicationId);
if (application == null) {
logger.debug("getGroup() Cant get application for id {}", applicationId);
return null;
}
logger.debug("GetGroup for {}/id {}", applicationId, groupId);
return application.getGroup(groupId);
}
} catch (CGateException e) {
logger.debug("GetGroup for id {}/{} failed {}", applicationId, groupId, e.getMessage());
}
return null;
}
private @Nullable CBusNetworkHandler getCBusNetworkHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device .");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof CBusNetworkHandler) {
return (CBusNetworkHandler) handler;
}
logger.debug("No available bridge handler found for bridge: {}", bridge.getUID());
return null;
}
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
/**
* The {@link CBusLightHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusLightHandler extends CBusGroupHandler {
private final Logger logger = LoggerFactory.getLogger(CBusLightHandler.class);
public CBusLightHandler(Thing thing) {
super(thing, CBusBindingConstants.CBUS_APPLICATION_LIGHTING);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Group group = this.group;
if (group == null) {
return;
}
if (command instanceof RefreshType) {
try {
int level = group.getLevel();
logger.debug("handle RefreshType Command for Chanell {} Group {} level {}", channelUID, groupId, level);
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_STATE)) {
updateState(channelUID, (level > 0) ? OnOffType.ON : OnOffType.OFF);
} else if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_LEVEL)) {
updateState(channelUID, new PercentType((int) (level * 100 / 255.0)));
}
} catch (CGateException e) {
logger.debug("Failed to getLevel for group {}", groupId, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
} else {
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_STATE)) {
logger.debug("Channel State command for {}: {}", channelUID, command);
if (command instanceof OnOffType) {
try {
if (command == OnOffType.ON) {
group.on();
} else if (command == OnOffType.OFF) {
group.off();
}
} catch (CGateException e) {
logger.debug("Failed to send command {} to {}", command, group, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
}
} else if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_LEVEL)) {
logger.debug("Channel Level command for {}: {}", channelUID, command);
try {
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
group.on();
} else if (command == OnOffType.OFF) {
group.off();
}
} else if (command instanceof PercentType) {
PercentType value = (PercentType) command;
group.ramp((int) Math.round(value.doubleValue() / 100 * 255), 0);
} else if (command instanceof IncreaseDecreaseType) {
int level = group.getLevel();
if (command == IncreaseDecreaseType.DECREASE) {
level = Math.max(level - 1, 0);
} else if (command == IncreaseDecreaseType.INCREASE) {
level = Math.min(level + 1, 255);
}
group.ramp(level, 0);
logger.debug("Change group level to {}", level);
}
} catch (CGateException e) {
logger.debug("Failed to send command {} to {}", command, group, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
}
}
}
public void updateGroup(int updateApplicationId, int updateGroupId, String value) {
if (updateGroupId == groupId && updateApplicationId == applicationId) {
Thing thing = getThing();
Channel channel = thing.getChannel(CBusBindingConstants.CHANNEL_STATE);
Channel channelLevel = thing.getChannel(CBusBindingConstants.CHANNEL_LEVEL);
if (channel != null && channelLevel != null) {
ChannelUID channelUID = channel.getUID();
ChannelUID channelLevelUID = channelLevel.getUID();
logger.debug("channel UID {} level UID {}", channelUID, channelLevelUID);
if ("on".equalsIgnoreCase(value) || "255".equalsIgnoreCase(value)) {
updateState(channelUID, OnOffType.ON);
updateState(channelLevelUID, new PercentType(100));
} else if ("off".equalsIgnoreCase(value) || "0".equalsIgnoreCase(value)) {
updateState(channelUID, OnOffType.OFF);
updateState(channelLevelUID, new PercentType(0));
} else {
try {
int v = Integer.parseInt(value);
updateState(channelUID, v > 0 ? OnOffType.ON : OnOffType.OFF);
updateState(channelLevelUID, new PercentType((int) (v * 100 / 255.0)));
} catch (NumberFormatException e) {
logger.warn("Invalid value presented to channel {}. Received {}, expected On/Off", channelUID,
value);
}
}
logger.debug("Updating CBus Lighting Group {} with value {}", thing.getUID(), value);
} else {
logger.debug("Failed to Update CBus Lighting Group {} with value {}: No Channel", thing.getUID(),
value);
}
}
}
}

View File

@@ -0,0 +1,289 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.cbus.internal.CBusNetworkConfiguration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.CGateSession;
import com.daveoxley.cbus.Network;
import com.daveoxley.cbus.Project;
/**
* The {@link CBusNetworkHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusNetworkHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(CBusNetworkHandler.class);
private @Nullable CBusNetworkConfiguration configuration;
private @Nullable Network network;
private @Nullable Project projectObject;
private @Nullable ScheduledFuture<?> initNetwork;
private @Nullable ScheduledFuture<?> networkSync;
public CBusNetworkHandler(Bridge thing) {
super(thing);
}
// This is abstract in base class so have to implement it.
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
@Override
public void initialize() {
logger.debug("initialize ");
configuration = getConfigAs(CBusNetworkConfiguration.class);
logger.debug("Using configuration {}", configuration);
CBusCGateHandler bridgeHandler = getCBusCGateHandler();
if (bridgeHandler == null || !bridgeHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
logger.debug("bridge not online");
updateStatus(ThingStatus.OFFLINE);
return;
}
logger.debug("Bridge online so init properly");
scheduler.execute(this::cgateOnline);
}
@Override
public void dispose() {
ScheduledFuture<?> networkSync = this.networkSync;
if (networkSync != null) {
networkSync.cancel(false);
}
ScheduledFuture<?> initNetwork = this.initNetwork;
if (initNetwork != null) {
initNetwork.cancel(false);
}
super.dispose();
}
public void cgateStateChanged(boolean isOnline) {
logger.debug("CgateStateChanged {}", isOnline);
if (!isOnline) {
network = null;
projectObject = null;
updateStatus();
} else {
cgateOnline();
}
}
private void cgateOnline() {
CBusNetworkConfiguration configuration = this.configuration;
if (configuration == null) {
logger.debug("cgateOnline - NetworkHandler not initialised");
return;
}
ThingStatus lastStatus = getThing().getStatus();
logger.debug("cgateOnline {}", lastStatus);
Integer networkID = configuration.id;
String project = configuration.project;
logger.debug("cgateOnline netid {} project {}", networkID, project);
Project projectObject = getProjectObject();
Network network = getNetwork();
logger.debug("network {}", network);
CBusCGateHandler cbusCGateHandler = getCBusCGateHandler();
if (cbusCGateHandler == null) {
logger.debug("NoCGateHandler");
return;
}
try {
if (projectObject == null) {
CGateSession session = cbusCGateHandler.getCGateSession();
if (session != null) {
try {
projectObject = (Project) session.getCGateObject("//" + project);
this.projectObject = projectObject;
} catch (CGateException ignore) {
// We dont need to do anything other than stop this propagating
}
}
if (projectObject == null) {
logger.debug("Cant get projectobject");
return;
}
}
if (network == null) {
CGateSession session = cbusCGateHandler.getCGateSession();
if (session != null) {
try {
network = (Network) session.getCGateObject("//" + project + "/" + networkID);
this.network = network;
} catch (CGateException ignore) {
// We dont need to do anything other than stop this propagating
}
}
if (network == null) {
logger.debug("cgateOnline: Cant get network");
return;
}
}
String state = network.getState();
logger.debug("Network state is {}", state);
if ("new".equals(state)) {
projectObject.start();
logger.debug("Need to wait for it to be synced");
} else if ("sync".equals(state)) {
logger.debug("Network is syncing so wait for it to be ok");
}
if (!"ok".equals(state)) {
ScheduledFuture<?> initNetwork = this.initNetwork;
if (initNetwork == null || initNetwork.isCancelled()) {
this.initNetwork = scheduler.scheduleWithFixedDelay(this::checkNetworkOnline, 30, 30,
TimeUnit.SECONDS);
logger.debug("Schedule a check every minute");
} else {
logger.debug("initNetwork alreadys started");
}
updateStatus();
return;
}
} catch (CGateException e) {
logger.warn("Cannot load C-Bus network {}", networkID, e);
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR);
}
updateStatus();
}
private void checkNetworkOnline() {
Network network = getNetwork();
try {
if (network != null && network.isOnline()) {
logger.debug("Network is online");
ScheduledFuture<?> initNetwork = this.initNetwork;
if (initNetwork != null) {
initNetwork.cancel(false);
this.initNetwork = null;
}
} else {
ThingStatus lastStatus = getThing().getStatus();
logger.debug("Network still not online {}", lastStatus);
}
} catch (CGateException e) {
logger.warn("Cannot check if network is online {} ", network.getNetworkID());
}
updateStatus();
}
private void updateStatus() {
CBusNetworkConfiguration configuration = this.configuration;
if (configuration == null) {
logger.debug("updateStatus - NetworkHandler not initialised");
return;
}
ThingStatus lastStatus = getThing().getStatus();
Network network = getNetwork();
CBusCGateHandler cbusCGateHandler = getCBusCGateHandler();
try {
if (cbusCGateHandler == null || !cbusCGateHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "CGate connection offline");
} else if (network == null) {
logger.debug("No network - set configuration error");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No Network object available");
} else if (network.isOnline()) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Network is not reporting online");
}
} catch (CGateException e) {
logger.warn("Problem checking network state for network {}", network.getNetworkID(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
if (!getThing().getStatus().equals(lastStatus)) {
ScheduledFuture<?> networkSync = this.networkSync;
if (lastStatus == ThingStatus.OFFLINE) {
if (networkSync == null || networkSync.isCancelled()) {
this.networkSync = scheduler.scheduleWithFixedDelay(this::doNetworkSync, 10,
configuration.syncInterval, TimeUnit.SECONDS);
}
} else {
if (networkSync != null) {
networkSync.cancel(false);
}
}
for (Thing thing : getThing().getThings()) {
ThingHandler handler = thing.getHandler();
if (handler instanceof CBusGroupHandler) {
((CBusGroupHandler) handler).updateStatus();
}
}
}
}
private void doNetworkSync() {
Network network = getNetwork();
try {
if (getThing().getStatus().equals(ThingStatus.ONLINE) && network != null) {
logger.info("Starting network sync on network {}", network.getNetworkID());
network.startSync();
}
} catch (CGateException e) {
logger.warn("Cannot start network sync on network {} - {}", network.getNetworkID(), e.getMessage());
}
}
private @Nullable CBusCGateHandler getCBusCGateHandler() {
logger.debug("getCBusCGateHandler");
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device.");
return null;
}
ThingHandler handler = bridge.getHandler();
if (handler instanceof CBusCGateHandler) {
return (CBusCGateHandler) handler;
} else {
logger.debug("No available bridge handler found for bridge: {}.", bridge.getUID());
return null;
}
}
public @Nullable Network getNetwork() {
return network;
}
public int getNetworkId() {
CBusNetworkConfiguration configuration = this.configuration;
if (configuration == null) {
logger.debug("getNetworkId - NetworkHandler not initialised");
return -1;
}
return configuration.id;
}
private @Nullable Project getProjectObject() {
return projectObject;
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
/**
* The {@link CBusTemperatureHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusTemperatureHandler extends CBusGroupHandler {
private final Logger logger = LoggerFactory.getLogger(CBusTemperatureHandler.class);
public CBusTemperatureHandler(Thing thing) {
super(thing, CBusBindingConstants.CBUS_APPLICATION_TEMPERATURE);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Read only thing - no commands to handle
if (command instanceof RefreshType) {
try {
Group group = this.group;
if (group != null) {
int level = group.getLevel();
logger.debug("handle RefreshType Command for Chanell {} Group {} level {}", channelUID, groupId,
level);
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_TEMP)) {
updateState(channelUID, new QuantityType<>(level, CELSIUS));
}
}
} catch (CGateException e) {
logger.debug("Failed to getLevel for group {}", groupId, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
}
}
public void updateGroup(int updateApplicationId, int updateGroupId, String value) {
if (updateGroupId == groupId && updateApplicationId == applicationId) {
Thing thing = getThing();
Channel channel = thing.getChannel(CBusBindingConstants.CHANNEL_TEMP);
if (channel != null) {
ChannelUID channelUID = channel.getUID();
updateState(channelUID, new QuantityType<>(Double.parseDouble(value), CELSIUS));
logger.trace("Updating CBus Temperature Group {} with value {}", thing.getUID(), value);
} else {
logger.trace("Failed to Update CBus Temperature Group {} with value {}: No Channel", thing.getUID(),
value);
}
}
}
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) 2010-2020 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.cbus.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
/**
* The {@link CBusTriggerHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusTriggerHandler extends CBusGroupHandler {
private final Logger logger = LoggerFactory.getLogger(CBusTriggerHandler.class);
public CBusTriggerHandler(Thing thing) {
super(thing, CBusBindingConstants.CBUS_APPLICATION_TRIGGER);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Group group = this.group;
if (group == null) {
return;
}
if (command instanceof RefreshType) {
/*
* Cgate cant provide the current value for a trigger group
*/
logger.debug("Refresh for Trigger group not implemented");
} else {
if (channelUID.getId().equals(CBusBindingConstants.CHANNEL_VALUE)) {
logger.debug("Channel Value command for {}: {}", channelUID, command);
try {
if (command instanceof DecimalType) {
group.TriggerEvent(((DecimalType) command).intValue());
}
} catch (CGateException e) {
logger.debug("Failed to send trigger command {} to {}", command, group, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Communication Error");
}
}
}
}
public void updateGroup(int updateApplicationId, int updateGroupId, String value) {
if (updateGroupId == groupId && updateApplicationId == applicationId) {
Thing thing = getThing();
Channel channel = thing.getChannel(CBusBindingConstants.CHANNEL_VALUE);
if (channel != null) {
ChannelUID channelUID = channel.getUID();
DecimalType val = new DecimalType(value);
updateState(channelUID, val);
logger.trace("Updating CBus Trigger Group {} with value {}", thing.getUID(), value);
}
}
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration class for {@link CBusCGateConfiguration}.
*
* @author John Harvey - Initial contribution
*/
@NonNullByDefault
public class CBusCGateConfiguration {
public String ipAddress = "";
@Override
public String toString() {
return String.format("[ipAddress=%s]", ipAddress);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration class for {@link CBusGroupConfiguration}.
*
* @author John Harvey - Initial contribution
*/
@NonNullByDefault
public class CBusGroupConfiguration {
public int group;
@Override
public String toString() {
return String.format("[group=%d]", group);
}
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal;
import java.util.Hashtable;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.binding.cbus.handler.CBusCGateHandler;
import org.openhab.binding.cbus.handler.CBusDaliHandler;
import org.openhab.binding.cbus.handler.CBusLightHandler;
import org.openhab.binding.cbus.handler.CBusNetworkHandler;
import org.openhab.binding.cbus.handler.CBusTemperatureHandler;
import org.openhab.binding.cbus.handler.CBusTriggerHandler;
import org.openhab.binding.cbus.internal.discovery.CBusGroupDiscovery;
import org.openhab.binding.cbus.internal.discovery.CBusNetworkDiscovery;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
/**
* The {@link CBusHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.cbus")
public class CBusHandlerFactory extends BaseThingHandlerFactory {
private @Nullable ServiceRegistration<?> cbusCGateHandlerServiceReg = null;
private @Nullable ServiceRegistration<?> cbusNetworkHandlerServiceReg = null;
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return CBusBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (CBusBindingConstants.BRIDGE_TYPE_CGATE.equals(thingTypeUID)) {
CBusCGateHandler handler = new CBusCGateHandler((Bridge) thing);
registerDeviceDiscoveryService(handler);
return handler;
}
if (CBusBindingConstants.BRIDGE_TYPE_NETWORK.equals(thingTypeUID)) {
CBusNetworkHandler handler = new CBusNetworkHandler((Bridge) thing);
registerDeviceDiscoveryService(handler);
return handler;
}
if (CBusBindingConstants.THING_TYPE_LIGHT.equals(thingTypeUID)) {
return new CBusLightHandler(thing);
}
if (CBusBindingConstants.THING_TYPE_TEMPERATURE.equals(thingTypeUID)) {
return new CBusTemperatureHandler(thing);
}
if (CBusBindingConstants.THING_TYPE_TRIGGER.equals(thingTypeUID)) {
return new CBusTriggerHandler(thing);
}
if (CBusBindingConstants.THING_TYPE_DALI.equals(thingTypeUID)) {
return new CBusDaliHandler(thing);
}
return null;
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof CBusCGateHandler) {
ServiceRegistration<?> serviceReg = this.cbusCGateHandlerServiceReg;
if (serviceReg != null) {
serviceReg.unregister();
}
} else if (thingHandler instanceof CBusNetworkHandler) {
ServiceRegistration<?> serviceReg = this.cbusNetworkHandlerServiceReg;
if (serviceReg != null) {
serviceReg.unregister();
}
}
super.removeHandler(thingHandler);
}
private void registerDeviceDiscoveryService(CBusCGateHandler cbusCgateHandler) {
CBusNetworkDiscovery discoveryService = new CBusNetworkDiscovery(cbusCgateHandler);
cbusCGateHandlerServiceReg = super.bundleContext.registerService(DiscoveryService.class.getName(),
discoveryService, new Hashtable<String, Object>());
}
private void registerDeviceDiscoveryService(CBusNetworkHandler cbusNetworkHandler) {
CBusGroupDiscovery discoveryService = new CBusGroupDiscovery(cbusNetworkHandler);
cbusNetworkHandlerServiceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService,
new Hashtable<String, Object>());
}
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Configuration class for {@link CBusNetworkConfiguration}.
*
* @author John Harvey - Initial contribution
*/
@NonNullByDefault
public class CBusNetworkConfiguration {
public int id;
public String project = "";
public int syncInterval;
@Override
public String toString() {
return String.format("[id=%d, project=%s, syncInterval=%d]", id, project, syncInterval);
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.common.ThreadPoolManager;
import com.daveoxley.cbus.CGateThreadPool;
import com.daveoxley.cbus.CGateThreadPoolExecutor;
/**
* The {@link CBusThreadPool} is responsible for executing jobs from a threadpool
*
* @author John Harvey - Initial contribution
*/
@NonNullByDefault
public class CBusThreadPool extends CGateThreadPool {
private final Map<String, @Nullable CGateThreadPoolExecutor> executorMap = new HashMap<>();
@Override
protected synchronized CGateThreadPoolExecutor CreateExecutor(@Nullable String name) {
if (name == null || name.isEmpty()) {
name = "_default";
}
@Nullable
CGateThreadPoolExecutor executor = executorMap.get(name);
if (executor != null) {
return executor;
}
CGateThreadPoolExecutor newExecutor = new CBusThreadPoolExecutor(name);
executorMap.put(name, newExecutor);
return newExecutor;
}
@NonNullByDefault
public class CBusThreadPoolExecutor extends CGateThreadPoolExecutor {
private final ThreadPoolExecutor threadPool;
public CBusThreadPoolExecutor(@Nullable String poolName) {
threadPool = (ThreadPoolExecutor) ThreadPoolManager.getPool("binding.cbus-" + poolName);
}
@Override
protected void execute(@Nullable Runnable runnable) {
if (runnable != null) {
threadPool.execute(runnable);
}
}
}
}

View File

@@ -0,0 +1,112 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal.discovery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.binding.cbus.handler.CBusNetworkHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.Application;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Group;
import com.daveoxley.cbus.Network;
/**
* The {@link CBusGroupDiscovery} class is used to discover CBus
* groups that are in the CBus Network
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusGroupDiscovery extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(CBusGroupDiscovery.class);
private final CBusNetworkHandler cbusNetworkHandler;
public CBusGroupDiscovery(CBusNetworkHandler cbusNetworkHandler) {
super(CBusBindingConstants.SUPPORTED_THING_TYPES_UIDS, 30, false);
this.cbusNetworkHandler = cbusNetworkHandler;
}
@Override
protected synchronized void startScan() {
if (cbusNetworkHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
ThingUID bridgeUid = cbusNetworkHandler.getThing().getBridgeUID();
if (bridgeUid == null) {
scanFinished();
return;
}
try {
Map<Integer, ThingTypeUID> applications = new HashMap<Integer, ThingTypeUID>();
applications.put(CBusBindingConstants.CBUS_APPLICATION_LIGHTING, CBusBindingConstants.THING_TYPE_LIGHT);
applications.put(CBusBindingConstants.CBUS_APPLICATION_DALI, CBusBindingConstants.THING_TYPE_DALI);
applications.put(CBusBindingConstants.CBUS_APPLICATION_TEMPERATURE,
CBusBindingConstants.THING_TYPE_TEMPERATURE);
applications.put(CBusBindingConstants.CBUS_APPLICATION_TRIGGER,
CBusBindingConstants.THING_TYPE_TRIGGER);
Network network = cbusNetworkHandler.getNetwork();
if (network == null) {
scanFinished();
return;
}
for (Map.Entry<Integer, ThingTypeUID> applicationItem : applications.entrySet()) {
Application application = network.getApplication(applicationItem.getKey());
if (application == null) {
continue;
}
ArrayList<Group> groups = application.getGroups(false);
for (Group group : groups) {
logger.debug("Found group: {} {} {}", application.getName(), group.getGroupID(),
group.getName());
Map<String, Object> properties = new HashMap<>();
properties.put(CBusBindingConstants.PROPERTY_APPLICATION_ID,
Integer.toString(applicationItem.getKey()));
properties.put(CBusBindingConstants.CONFIG_GROUP_ID, Integer.toString(group.getGroupID()));
properties.put(CBusBindingConstants.PROPERTY_GROUP_NAME, group.getName());
properties.put(CBusBindingConstants.PROPERTY_NETWORK_ID,
Integer.toString(network.getNetworkID()));
ThingUID uid = new ThingUID(applicationItem.getValue(), Integer.toString(group.getGroupID()),
bridgeUid.getId(), cbusNetworkHandler.getThing().getUID().getId());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel("CBUS " + group.getName() + "(" + group.getGroupID() + ")")
.withBridge(cbusNetworkHandler.getThing().getUID()).build();
thingDiscovered(result);
}
}
} catch (CGateException e) {
logger.debug("Failed to discover groups", e);
}
}
scanFinished();
}
private synchronized void scanFinished() {
stopScan();// this notifies the scan listener that the scan is finished
abortScan();// this clears the scheduled call to stopScan
}
}

View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2010-2020 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.cbus.internal.discovery;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.cbus.CBusBindingConstants;
import org.openhab.binding.cbus.handler.CBusCGateHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.daveoxley.cbus.CGateException;
import com.daveoxley.cbus.Network;
/**
* The {@link CBusNetworkDiscovery} class is used to discover CBus
* networks that are in the CBus Project
*
* @author Scott Linton - Initial contribution
*/
@NonNullByDefault
public class CBusNetworkDiscovery extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(CBusNetworkDiscovery.class);
private final CBusCGateHandler cBusCGateHandler;
public CBusNetworkDiscovery(CBusCGateHandler cBusCGateHandler) {
super(CBusBindingConstants.NETWORK_DISCOVERY_THING_TYPES_UIDS, 60, false);
this.cBusCGateHandler = cBusCGateHandler;
}
@Override
protected void startScan() {
if (cBusCGateHandler.getThing().getStatus().equals(ThingStatus.ONLINE)) {
try {
ArrayList<Network> networks = Network.listAll(cBusCGateHandler.getCGateSession(), false);
for (Network network : networks) {
logger.debug("Found Network: {} {}", network.getNetworkID(), network.getName());
Map<String, Object> properties = new HashMap<>(3);
properties.put(CBusBindingConstants.CONFIG_NETWORK_ID, network.getNetworkID());
properties.put(CBusBindingConstants.PROPERTY_NETWORK_NAME, network.getName());
properties.put(CBusBindingConstants.CONFIG_NETWORK_PROJECT, network.getProjectName());
ThingUID uid = new ThingUID(CBusBindingConstants.BRIDGE_TYPE_NETWORK,
network.getProjectName().toLowerCase().replace(" ", "_") + network.getNetworkID(),
cBusCGateHandler.getThing().getUID().getId());
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties)
.withLabel(
network.getProjectName() + "/" + network.getNetworkID() + " - " + network.getName())
.withBridge(cBusCGateHandler.getThing().getUID()).build();
thingDiscovered(result);
}
} catch (CGateException e) {
logger.warn("Failed to discover networks", e);
}
}
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="cbus" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>C-Bus Binding</name>
<description>Clipsal C-Bus Binding</description>
<author>Scott Linton</author>
</binding:binding>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="cbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="cgate">
<label>CGate Connection</label>
<description>CGate Connection to connect to physical C-Bus Networks</description>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>CGate Server IP</label>
<description>The IP address of the CGate Server.</description>
</parameter>
</config-description>
</bridge-type>
<bridge-type id="network">
<label>C-Bus Network</label>
<description>C-Bus Network bridge</description>
<config-description>
<parameter name="id" type="integer" required="true">
<label>C-Bus Network ID</label>
<description>Network number of C-Bus Network.</description>
</parameter>
<parameter name="project" type="text" required="true">
<label>C-Bus Project Name</label>
<description>Project name that contains the network.</description>
</parameter>
<parameter name="syncInterval" type="integer" required="false" min="120" unit="s">
<label>Network Sync Interval</label>
<description>The interval in seconds between fetching current group states from the network.</description>
<default>600</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="cbus"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="light">
<supported-bridge-type-refs>
<bridge-type-ref id="network"/>
</supported-bridge-type-refs>
<label>Lighting Group</label>
<description>Lighting Group</description>
<channels>
<channel id="state" typeId="light-channel"/>
<channel id="level" typeId="level-channel"/>
</channels>
<properties>
<property name="CBUS Network Id"/>
<property name="CBUS Application Id"/>
<property name="CBUS Group Name"/>
</properties>
<config-description>
<parameter name="group" type="integer" required="true">
<label>CBus Group ID</label>
<description>Group number of this Thing on the C-Bus Network.</description>
<default>-1</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="temperature">
<supported-bridge-type-refs>
<bridge-type-ref id="network"/>
</supported-bridge-type-refs>
<label>Temperature Group</label>
<description>Temperature Group</description>
<channels>
<channel id="temp" typeId="temp-channel"/>
</channels>
<properties>
<property name="CBUS Network Id"/>
<property name="CBUS Application Id"/>
<property name="CBUS Group Name"/>
</properties>
<config-description>
<parameter name="group" type="integer" required="true">
<label>CBus Group ID</label>
<description>Group number of this Thing on the C-Bus Network.</description>
<default>-1</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="trigger">
<supported-bridge-type-refs>
<bridge-type-ref id="network"/>
</supported-bridge-type-refs>
<label>Trigger Group</label>
<description>Trigger Group</description>
<channels>
<channel id="value" typeId="trigger-channel"/>
</channels>
<properties>
<property name="CBUS Network Id"/>
<property name="CBUS Application Id"/>
<property name="CBUS Group Name"/>
</properties>
<config-description>
<parameter name="group" type="integer" required="true">
<label>CBus Group ID</label>
<description>Group number of this Thing on the C-Bus Network.</description>
<default>-1</default>
</parameter>
</config-description>
</thing-type>
<thing-type id="dali">
<supported-bridge-type-refs>
<bridge-type-ref id="network"/>
</supported-bridge-type-refs>
<label>DALI Group</label>
<description>DALI Group</description>
<channels>
<channel id="level" typeId="dali-channel"/>
</channels>
<properties>
<property name="CBUS Network Id"/>
<property name="CBUS Application Id"/>
<property name="CBUS Group Name"/>
</properties>
<config-description>
<parameter name="group" type="integer" required="true">
<label>CBus Group ID</label>
<description>Group number of this Thing on the C-Bus Network.</description>
<default>-1</default>
</parameter>
</config-description>
</thing-type>
<!-- Lighting Group Channel Type -->
<channel-type id="light-channel">
<item-type>Switch</item-type>
<label>Light Channel</label>
<description>Group channel for CBus on/off lighting groups</description>
<category>Light</category>
</channel-type>
<channel-type id="level-channel">
<item-type>Dimmer</item-type>
<label>Level Channel</label>
<description>Group channel for CBus variable value lighting groups</description>
<category>DimmableLight</category>
</channel-type>
<!-- Temperature Group Channel Type -->
<channel-type id="temp-channel">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Group channel for CBus temperature groups</description>
<category>Temperature</category>
<state pattern="%.1f %unit%" readOnly="true"/>
</channel-type>
<!-- Trigger Group Channel Type -->
<channel-type id="trigger-channel">
<item-type>Number</item-type>
<label>Trigger Channel</label>
<description>Group channel for CBus trigger groups</description>
</channel-type>
<!-- DALI Group Channel Type -->
<channel-type id="dali-channel">
<item-type>Dimmer</item-type>
<label>DALI Channel</label>
<description>Group channel for CBus DALI groups</description>
</channel-type>
</thing:thing-descriptions>