mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[luxom] Initial contribution (#12310)
Signed-off-by: Kris Jespers <kriasoft@telenet.be>
This commit is contained in:
parent
707ecaf47b
commit
873d615316
@ -169,6 +169,7 @@
|
||||
/bundles/org.openhab.binding.loxone/ @ppieczul
|
||||
/bundles/org.openhab.binding.luftdateninfo/ @weymann
|
||||
/bundles/org.openhab.binding.lutron/ @actong @bobadair
|
||||
/bundles/org.openhab.binding.luxom/ @jesperskriasoft
|
||||
/bundles/org.openhab.binding.luxtronikheatpump/ @sgiehl
|
||||
/bundles/org.openhab.binding.magentatv/ @markus7017
|
||||
/bundles/org.openhab.binding.mail/ @openhab/add-ons-maintainers
|
||||
|
@ -836,6 +836,11 @@
|
||||
<artifactId>org.openhab.binding.lutron</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.luxom</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.luxtronikheatpump</artifactId>
|
||||
|
13
bundles/org.openhab.binding.luxom/NOTICE
Normal file
13
bundles/org.openhab.binding.luxom/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
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/openhab-addons
|
98
bundles/org.openhab.binding.luxom/README.md
Normal file
98
bundles/org.openhab.binding.luxom/README.md
Normal file
@ -0,0 +1,98 @@
|
||||
# Luxom Binding
|
||||
|
||||
This binding integrates with a https://luxom.io/ based system through a Luxom IP interface module.
|
||||
The binding has been tested with the DS65L IP interface, but it's not an official binding by Luxom.
|
||||
|
||||
The API implementation is based on the following documentation:
|
||||
|
||||
* https://old.luxom.io/uploads/ppfiles/27/LUXOM_ASCII.pdf
|
||||
* https://old.luxom.io/uploads/ppfiles/28/LUXOM_ASCII_extended.pdf
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding currently supports the following thing types:
|
||||
|
||||
* **ipbridge** - The Lutron main repeater/processor/hub
|
||||
* **dimmer** - Light dimmer
|
||||
* **switch** - Switch or relay module
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Bridge
|
||||
|
||||
The Bridge thing has two parameters:
|
||||
|
||||
- ipAddress: This is the IP address of the IP interface module
|
||||
- port: The listening port (optional, defaults to 2300)
|
||||
|
||||
```
|
||||
Bridge luxom:bridge:myhouse [ ipAddress="192.168.0.50", port="2300"] {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Devices
|
||||
|
||||
Each device has an address on the Luxom bus, this address must be specified in the 'address' parameter.
|
||||
You will have to look it up in your documentation or in the 'Luxom Plusconfig' software.
|
||||
|
||||
Sometimes a device does not send back a confirmation over the bus having set the correct state.
|
||||
Some dimmers do the dimming, but do not send back the set brightness level.
|
||||
To be able to use these devices, you can add the `doesNotReply=true` parameter so that the binding immediately sets the item's state and does not wait for confirmation.
|
||||
|
||||
#### Dimmers
|
||||
|
||||
Dimmers support the optional advanced parameters `onLevel`, `onToLast` and `stepPercentage`:
|
||||
|
||||
* The `onLevel` parameter specifies the level to which the dimmer will go when sent an ON command. It defaults to 100.
|
||||
* The `onToLast` parameter is a boolean that defaults to false. If set to "true", the dimmer will go to its last non-zero level when sent an ON command. If the last non-zero level cannot be determined, the value of `onLevel` will be used instead.
|
||||
* The `stepPercentage` specifies the in-/decrease in percentage of brightness. Default is 5.
|
||||
|
||||
A **dimmer** thing has a single channel *Lighting.Brightness* with type Dimmer and category DimmableLight.
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
```
|
||||
Thing dimmer dimmerLightLiving1 [address="A,02", onLevel="50", onToLast="false", stepPercentage="5"]
|
||||
```
|
||||
|
||||
#### Switches
|
||||
|
||||
Switches take no additional parameters.
|
||||
A **switch** thing has a single channel *switch* with type Switch and category Switch.
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
```
|
||||
Thing switch switchLiving1 [address="A,02"]
|
||||
```
|
||||
|
||||
### Channels
|
||||
|
||||
The following is a summary of channels for all Luxom things:
|
||||
|
||||
| Thing | Channel | Item Type | Description |
|
||||
|---------------------|----------------|---------------|-----------------------------------|
|
||||
| dimmer | brightness | Dimmer | Increase/decrease the light level |
|
||||
| switch | switch | Switch | Switch the device on/off |
|
||||
|
||||
|
||||
### Full Example
|
||||
|
||||
demo.things:
|
||||
|
||||
```
|
||||
Bridge luxom:bridge:myhouse [ ipAddress="192.168.0.50", port="2300"] {
|
||||
Thing switch switchBedroom1 "Switch 1" @ "Bedroom" [address="1,01"]
|
||||
Thing dimmer dimmerBedroom1 "dimmer 1" @ "Bedroom" [address="A,02"]
|
||||
Thing dimmer dimmerKitchen1 "dimmer 1" @ "Kitchen" [address="A,04", doesNotReply=true]
|
||||
}
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Dimmer FF_Bedroom_Lights "Bedroom dimmer light" <light> (FF_Living, gLight) ["Lighting"] {channel="luxom:dimmer:myhouse:dimmerBedroom1:brightness", ga="Light", homekit="Lighting, Lighting.Brightness"}
|
||||
Switch FF_Bedroom_PowerOutlet1 "Bedroom Power Outlet 1" <poweroutlet> (FF_Living, gPower) ["Switchable"] {channel="luxom:switch:myhouse:switchBedroom1:switch", ga="Outlet"}
|
||||
Dimmer FF_Kitchen_Lights "Kitchen dimmer light" <light> (FF_Kitchen, gLight) ["Lighting"] {channel="luxom:dimmer:myhouse:dimmerKitchen1:brightness", ga="Light", homekit="Lighting, Lighting.Brightness"}
|
||||
```
|
17
bundles/org.openhab.binding.luxom/pom.xml
Normal file
17
bundles/org.openhab.binding.luxom/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
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.reactor.bundles</artifactId>
|
||||
<version>3.3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.luxom</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Luxom Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.luxom-${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-luxom" description="Luxom Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.luxom/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link LuxomBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "luxom";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
|
||||
// bridge
|
||||
public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge");
|
||||
|
||||
// generic thing types
|
||||
public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch");
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(BRIDGE_THING_TYPE, THING_TYPE_SWITCH,
|
||||
THING_TYPE_DIMMER);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_BRIGHTNESS = "brightness";
|
||||
public static final String CHANNEL_SWITCH = "switch";
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.luxom.internal.handler.LuxomBridgeHandler;
|
||||
import org.openhab.binding.luxom.internal.handler.LuxomDimmerHandler;
|
||||
import org.openhab.binding.luxom.internal.handler.LuxomSwitchHandler;
|
||||
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.service.component.annotations.Component;
|
||||
|
||||
/**
|
||||
* The {@link LuxomHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.luxom", service = ThingHandlerFactory.class)
|
||||
public class LuxomHandlerFactory extends BaseThingHandlerFactory {
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return LuxomBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
if (LuxomBindingConstants.BRIDGE_THING_TYPE.equals(thing.getThingTypeUID())) {
|
||||
return new LuxomBridgeHandler((Bridge) thing);
|
||||
} else if (LuxomBindingConstants.THING_TYPE_SWITCH.equals(thing.getThingTypeUID())) {
|
||||
return new LuxomSwitchHandler(thing);
|
||||
} else if (LuxomBindingConstants.THING_TYPE_DIMMER.equals(thing.getThingTypeUID())) {
|
||||
return new LuxomDimmerHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommandExecutionSpecification {
|
||||
private final String command;
|
||||
|
||||
public CommandExecutionSpecification(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
}
|
@ -0,0 +1,345 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.luxom.internal.handler.config.LuxomBridgeConfig;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomAction;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomCommand;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomCommunication;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomSystemInfo;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with the main Luxom IP access module.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomBridgeHandler extends BaseBridgeHandler {
|
||||
public static final int HEARTBEAT_INTERVAL_SECONDS = 50;
|
||||
private final LuxomSystemInfo systemInfo;
|
||||
|
||||
private static final int DEFAULT_RECONNECT_INTERVAL_IN_MINUTES = 1;
|
||||
private static final long HEARTBEAT_ACK_TIMEOUT_SECONDS = 20;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LuxomBridgeHandler.class);
|
||||
|
||||
private @Nullable LuxomBridgeConfig config;
|
||||
private final AtomicInteger nrOfSendPermits = new AtomicInteger(0);
|
||||
private int reconnectInterval;
|
||||
|
||||
private @Nullable LuxomCommand previousCommand;
|
||||
private final LuxomCommunication communication;
|
||||
private final BlockingQueue<List<CommandExecutionSpecification>> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private @Nullable Thread messageSender;
|
||||
private @Nullable ScheduledFuture<?> heartBeat;
|
||||
private @Nullable ScheduledFuture<?> heartBeatTimeoutTask;
|
||||
private @Nullable ScheduledFuture<?> connectRetryJob;
|
||||
|
||||
public @Nullable LuxomBridgeConfig getIPBridgeConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public LuxomBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
logger.debug("Luxom bridge init");
|
||||
systemInfo = new LuxomSystemInfo();
|
||||
communication = new LuxomCommunication(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("Bridge received command {} for {}", command.toFullString(), channelUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfig().as(LuxomBridgeConfig.class);
|
||||
|
||||
if (validConfiguration(config)) {
|
||||
reconnectInterval = (config.reconnectInterval > 0) ? config.reconnectInterval
|
||||
: DEFAULT_RECONNECT_INTERVAL_IN_MINUTES;
|
||||
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.connecting");
|
||||
scheduler.submit(this::connect); // start the async connect task
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validConfiguration(@Nullable LuxomBridgeConfig config) {
|
||||
if (config == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/bridge-configuration-missing");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.ipAddress == null || config.ipAddress.trim().isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/bridge-address-missing");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void scheduleConnectRetry(long waitMinutes) {
|
||||
logger.debug("Scheduling connection retry in {} (minutes)", waitMinutes);
|
||||
connectRetryJob = scheduler.schedule(this::connect, waitMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
if (communication.isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
logger.debug("Connecting to bridge at {}", config.ipAddress);
|
||||
}
|
||||
|
||||
try {
|
||||
communication.startCommunication();
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
disconnect();
|
||||
scheduleConnectRetry(reconnectInterval); // Possibly a temporary problem. Try again later.
|
||||
}
|
||||
}
|
||||
|
||||
public void startProcessing() {
|
||||
nrOfSendPermits.set(1);
|
||||
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
messageSender = new Thread(this::sendCommandsThread, "Luxom sender");
|
||||
messageSender.start();
|
||||
|
||||
logger.debug("Starting heartbeat job with interval {} (seconds)", HEARTBEAT_INTERVAL_SECONDS);
|
||||
heartBeat = scheduler.scheduleWithFixedDelay(this::sendHeartBeat, 10, HEARTBEAT_INTERVAL_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void sendCommandsThread() {
|
||||
logger.debug("Starting send commands thread...");
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
logger.debug("waiting for command to send...");
|
||||
List<CommandExecutionSpecification> commands = sendQueue.take();
|
||||
|
||||
try {
|
||||
for (CommandExecutionSpecification commandExecutionSpecification : commands) {
|
||||
communication.sendMessage(commandExecutionSpecification.getCommand());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("Communication error while sending, will try to reconnect. Error: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
|
||||
reconnect();
|
||||
|
||||
// reconnect() will start a new thread; terminate this one
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void disconnect() {
|
||||
logger.debug("Disconnecting from bridge");
|
||||
|
||||
if (connectRetryJob != null) {
|
||||
connectRetryJob.cancel(true);
|
||||
}
|
||||
|
||||
if (this.heartBeat != null) {
|
||||
this.heartBeat.cancel(true);
|
||||
}
|
||||
|
||||
cancelCheckAliveTimeoutTask();
|
||||
|
||||
if (messageSender != null && messageSender.isAlive()) {
|
||||
messageSender.interrupt();
|
||||
}
|
||||
|
||||
this.communication.stopCommunication();
|
||||
}
|
||||
|
||||
public void reconnect() {
|
||||
reconnect(false);
|
||||
}
|
||||
|
||||
private synchronized void reconnect(boolean timeout) {
|
||||
if (timeout) {
|
||||
logger.debug("Keepalive timeout, attempting to reconnect to the bridge");
|
||||
} else {
|
||||
logger.debug("Connection problem, attempting to reconnect to the bridge");
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
|
||||
public void sendCommands(List<CommandExecutionSpecification> commands) {
|
||||
this.sendQueue.add(commands);
|
||||
}
|
||||
|
||||
private @Nullable LuxomThingHandler findThingHandler(@Nullable String address) {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (thing.getHandler() instanceof LuxomThingHandler) {
|
||||
LuxomThingHandler handler = (LuxomThingHandler) thing.getHandler();
|
||||
|
||||
try {
|
||||
if (handler != null && handler.getAddress().equals(address)) {
|
||||
return handler;
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
logger.trace("Handler for id {} not initialized", address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* needed with fast reconnect to update status of things
|
||||
*/
|
||||
public void forceRefreshThings() {
|
||||
for (Thing thing : getThing().getThings()) {
|
||||
if (thing.getHandler() instanceof LuxomThingHandler) {
|
||||
LuxomThingHandler handler = (LuxomThingHandler) thing.getHandler();
|
||||
handler.ping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendHeartBeat() {
|
||||
logger.trace("Sending heartbeat");
|
||||
// Reconnect if no response is received within KEEPALIVE_TIMEOUT_SECONDS.
|
||||
heartBeatTimeoutTask = scheduler.schedule(() -> reconnect(true), HEARTBEAT_ACK_TIMEOUT_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
sendCommands(List.of(new CommandExecutionSpecification(LuxomAction.HEARTBEAT.getCommand())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void thingUpdated(Thing thing) {
|
||||
LuxomBridgeConfig newConfig = thing.getConfiguration().as(LuxomBridgeConfig.class);
|
||||
boolean validConfig = validConfiguration(newConfig);
|
||||
boolean needsReconnect = validConfig && config != null && !config.sameConnectionParameters(newConfig);
|
||||
|
||||
if (!validConfig || needsReconnect) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
this.thing = thing;
|
||||
this.config = newConfig;
|
||||
|
||||
if (needsReconnect) {
|
||||
initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCommunicationError(IOException e) {
|
||||
logger.debug("Communication error while reading, will try to reconnect. Error: {}", e.getMessage());
|
||||
reconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
public void handleIncomingLuxomMessage(String luxomMessage) throws IOException {
|
||||
cancelCheckAliveTimeoutTask(); // we got a message
|
||||
|
||||
logger.trace("Luxom: received {}", luxomMessage);
|
||||
LuxomCommand luxomCommand = new LuxomCommand(luxomMessage);
|
||||
|
||||
// Now dispatch update to the proper thing handler
|
||||
|
||||
if (LuxomAction.PASSWORD_REQUEST == luxomCommand.getAction()) {
|
||||
communication.sendMessage(LuxomAction.REQUEST_FOR_INFORMATION.getCommand()); // direct send, no queue, so
|
||||
// no tcp flow constraint
|
||||
} else if (LuxomAction.MODULE_INFORMATION == luxomCommand.getAction()) {
|
||||
cmdSystemInfo(luxomCommand.getData());
|
||||
if (ThingStatus.ONLINE != getThing().getStatus()) {
|
||||
// this all happens before TCP flow controle, when startProcessing is called, TCP flow is activated...
|
||||
startProcessing();
|
||||
}
|
||||
} else if (LuxomAction.ACKNOWLEDGE == luxomCommand.getAction()) {
|
||||
logger.trace("received acknowledgement");
|
||||
} else if (LuxomAction.DATA == luxomCommand.getAction()
|
||||
|| LuxomAction.DATA_RESPONSE == luxomCommand.getAction()) {
|
||||
previousCommand = luxomCommand;
|
||||
} else if (LuxomAction.INVALID_ACTION != luxomCommand.getAction()) {
|
||||
if (LuxomAction.DATA_BYTE == luxomCommand.getAction()
|
||||
|| LuxomAction.DATA_BYTE_RESPONSE == luxomCommand.getAction()) {
|
||||
// data for previous command if it needs it
|
||||
if (previousCommand != null && previousCommand.getAction().isNeedsData()) {
|
||||
previousCommand.setData(luxomCommand.getData());
|
||||
luxomCommand = previousCommand;
|
||||
previousCommand = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (luxomCommand != null) {
|
||||
LuxomThingHandler handler = findThingHandler(luxomCommand.getAddress());
|
||||
|
||||
if (handler != null) {
|
||||
handler.handleCommandComingFromBridge(luxomCommand);
|
||||
} else {
|
||||
logger.warn("No handler found command {} for address : {}", luxomMessage,
|
||||
luxomCommand.getAddress());
|
||||
}
|
||||
} else {
|
||||
logger.warn("Something was wrong with the order of incoming commands, resulting command is null");
|
||||
}
|
||||
} else {
|
||||
logger.trace("Luxom: not handled {}", luxomMessage);
|
||||
}
|
||||
logger.trace("nrOfPermits after receive: {}", nrOfSendPermits.get());
|
||||
}
|
||||
|
||||
private void cancelCheckAliveTimeoutTask() {
|
||||
var task = heartBeatTimeoutTask;
|
||||
if (task != null) {
|
||||
// This method can be called from the keepAliveReconnect thread. Make sure
|
||||
// we don't interrupt ourselves, as that may prevent the reconnection attempt.
|
||||
task.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void cmdSystemInfo(@Nullable String info) {
|
||||
systemInfo.setSwVersion(info);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* exception during communication with luxom IP module
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomConnectionException extends Exception {
|
||||
private static final long serialVersionUID = 654654L;
|
||||
|
||||
public LuxomConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.luxom.internal.LuxomBindingConstants;
|
||||
import org.openhab.binding.luxom.internal.handler.config.LuxomThingDimmerConfig;
|
||||
import org.openhab.binding.luxom.internal.handler.util.PercentageConverter;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomAction;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomCommand;
|
||||
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.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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LuxomDimmerHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomDimmerHandler extends LuxomThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(LuxomDimmerHandler.class);
|
||||
|
||||
public LuxomDimmerHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
private @Nullable LuxomThingDimmerConfig config;
|
||||
private final AtomicReference<Integer> lastLightLevel = new AtomicReference<>(0);
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
config = getConfig().as(LuxomThingDimmerConfig.class);
|
||||
|
||||
logger.debug("Initializing Switch handler for address {}", getAddress());
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Switch {}", getAddress());
|
||||
@Nullable
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
} else if (ThingStatus.ONLINE.equals(bridge.getStatus())) {
|
||||
if (config != null && config.doesNotReply) {
|
||||
logger.debug("Switch {} will not reply, so always keeping it ONLINE", getAddress());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.awaiting-initial-response");
|
||||
ping(); // handleUpdate() will set thing status to online when response arrives
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("dimmer at address {} received command {} for {}", getAddress(), command.toFullString(),
|
||||
channelUID);
|
||||
if (LuxomBindingConstants.CHANNEL_SWITCH.equals(channelUID.getId())) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
set();
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
clear();
|
||||
}
|
||||
} else if (LuxomBindingConstants.CHANNEL_BRIGHTNESS.equals(channelUID.getId()) && config != null) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
logger.trace("dimmer at address {} just setting dimmer level", getAddress());
|
||||
dim(level);
|
||||
} else if (command instanceof IncreaseDecreaseType) {
|
||||
IncreaseDecreaseType s = (IncreaseDecreaseType) command;
|
||||
int currentValue = lastLightLevel.get();
|
||||
int newValue;
|
||||
if (IncreaseDecreaseType.INCREASE.equals(s)) {
|
||||
newValue = currentValue + config.stepPercentage;
|
||||
// round down to step multiple
|
||||
newValue = newValue - newValue % config.stepPercentage;
|
||||
logger.trace("dimmer at address {} just increasing dimmer level", getAddress());
|
||||
dim(newValue);
|
||||
} else {
|
||||
newValue = currentValue - config.stepPercentage;
|
||||
// round up to step multiple
|
||||
newValue = newValue + newValue % config.stepPercentage;
|
||||
logger.trace("dimmer at address {} just increasing dimmer level", getAddress());
|
||||
dim(Math.max(newValue, 0));
|
||||
}
|
||||
} else if (OnOffType.ON.equals(command)) {
|
||||
if (config.onToLast) {
|
||||
dim(lastLightLevel.get());
|
||||
} else {
|
||||
dim(config.onLevel.intValue());
|
||||
}
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
dim(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommandComingFromBridge(LuxomCommand command) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
if (LuxomAction.CLEAR_RESPONSE.equals(command.getAction())) {
|
||||
updateState(LuxomBindingConstants.CHANNEL_SWITCH, OnOffType.OFF);
|
||||
} else if (LuxomAction.SET_RESPONSE.equals(command.getAction())) {
|
||||
updateState(LuxomBindingConstants.CHANNEL_SWITCH, OnOffType.ON);
|
||||
} else if (LuxomAction.DATA_RESPONSE.equals(command.getAction())) {
|
||||
int percentage = PercentageConverter.getPercentage(command.getData());
|
||||
|
||||
lastLightLevel.set(percentage);
|
||||
updateState(LuxomBindingConstants.CHANNEL_BRIGHTNESS, new PercentType(percentage));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("dimmer at address {} linked to channel {}", getAddress(), channelUID);
|
||||
if (LuxomBindingConstants.CHANNEL_SWITCH.equals(channelUID.getId())
|
||||
|| LuxomBindingConstants.CHANNEL_BRIGHTNESS.equals(channelUID.getId())) {
|
||||
// Refresh state when new item is linked.
|
||||
if (config != null && !config.doesNotReply) {
|
||||
ping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* example : *A,0,2,2B;*Z,057;
|
||||
*/
|
||||
private void dim(int percentage) {
|
||||
logger.debug("dimming dimmer at address {} to {} %", getAddress(), percentage);
|
||||
List<CommandExecutionSpecification> commands = new ArrayList<>(3);
|
||||
if (percentage == 0) {
|
||||
commands.add(new CommandExecutionSpecification(LuxomAction.CLEAR.getCommand() + ",0," + getAddress()));
|
||||
} else {
|
||||
commands.add(new CommandExecutionSpecification(LuxomAction.SET.getCommand() + ",0," + getAddress()));
|
||||
}
|
||||
commands.add(new CommandExecutionSpecification(LuxomAction.DATA.getCommand() + ",0," + getAddress()));
|
||||
commands.add(new CommandExecutionSpecification(
|
||||
LuxomAction.DATA_BYTE.getCommand() + ",0" + PercentageConverter.getHexRepresentation(percentage)));
|
||||
|
||||
sendCommands(commands);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.luxom.internal.LuxomBindingConstants;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomAction;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomCommand;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LuxomSwitchHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomSwitchHandler extends LuxomThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(LuxomSwitchHandler.class);
|
||||
|
||||
public LuxomSwitchHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
logger.debug("Initializing Switch handler for address {}", getAddress());
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Switch {}", getAddress());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
} else if (ThingStatus.ONLINE.equals(bridge.getStatus())) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "@text/status.awaiting-initial-response");
|
||||
ping(); // handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.debug("switch at address {} received command {} for {}", getAddress(), command.toFullString(),
|
||||
channelUID);
|
||||
if (LuxomBindingConstants.CHANNEL_SWITCH.equals(channelUID.getId())) {
|
||||
if (OnOffType.ON.equals(command)) {
|
||||
set();
|
||||
ping(); // to make sure we know the current state
|
||||
} else if (OnOffType.OFF.equals(command)) {
|
||||
clear();
|
||||
ping(); // to make sure we know the current state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommandComingFromBridge(LuxomCommand command) {
|
||||
if (LuxomAction.CLEAR_RESPONSE.equals(command.getAction())) {
|
||||
updateState(LuxomBindingConstants.CHANNEL_SWITCH, OnOffType.OFF);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else if (LuxomAction.SET_RESPONSE.equals(command.getAction())) {
|
||||
updateState(LuxomBindingConstants.CHANNEL_SWITCH, OnOffType.ON);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("switch at address {} linked to channel {}", getAddress(), channelUID);
|
||||
if (LuxomBindingConstants.CHANNEL_SWITCH.equals(channelUID.getId())
|
||||
|| LuxomBindingConstants.CHANNEL_BRIGHTNESS.equals(channelUID.getId())) {
|
||||
// Refresh state when new item is linked.
|
||||
ping();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomAction;
|
||||
import org.openhab.binding.luxom.internal.protocol.LuxomCommand;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Base type for all Luxom thing handlers.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LuxomThingHandler extends BaseThingHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(LuxomThingHandler.class);
|
||||
|
||||
private String address = "";
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
String id = (String) getConfig().get("address");
|
||||
if (id == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/status.thing-address-missing");
|
||||
address = "noaddress";
|
||||
return;
|
||||
}
|
||||
address = id;
|
||||
}
|
||||
|
||||
public LuxomThingHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
public abstract void handleCommandComingFromBridge(LuxomCommand command);
|
||||
|
||||
public final String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries for any device state needed at initialization time or after losing connectivity to the bridge, and
|
||||
* updates device status. Will be called when bridge status changes to ONLINE and thing has status
|
||||
* OFFLINE:BRIDGE_OFFLINE.
|
||||
*/
|
||||
protected abstract void initDeviceState();
|
||||
|
||||
/**
|
||||
* Called when changing thing status to offline. Subclasses may override to take any needed actions.
|
||||
*/
|
||||
protected void thingOfflineNotify() {
|
||||
}
|
||||
|
||||
protected @Nullable LuxomBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
|
||||
return bridge == null ? null : (LuxomBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
logger.debug("Bridge status changed to {} for luxom device handler {}", bridgeStatusInfo.getStatus(),
|
||||
getAddress());
|
||||
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
|
||||
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
|
||||
initDeviceState();
|
||||
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
thingOfflineNotify();
|
||||
}
|
||||
}
|
||||
|
||||
protected void sendCommands(List<CommandExecutionSpecification> commands) {
|
||||
@Nullable
|
||||
LuxomBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR,
|
||||
"@text/status.bridge-handler-missing");
|
||||
thingOfflineNotify();
|
||||
} else {
|
||||
bridgeHandler.sendCommands(commands);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* example : *P,0,1,21;
|
||||
*/
|
||||
protected void ping() {
|
||||
sendCommands(List.of(new CommandExecutionSpecification(LuxomAction.PING.getCommand() + ",0," + getAddress())));
|
||||
}
|
||||
|
||||
/**
|
||||
* example : *S,0,1,21;
|
||||
*/
|
||||
protected void set() {
|
||||
sendCommands(List.of(new CommandExecutionSpecification(LuxomAction.SET.getCommand() + ",0," + getAddress())));
|
||||
}
|
||||
|
||||
/**
|
||||
* example : *C,0,1,21;
|
||||
*/
|
||||
protected void clear() {
|
||||
sendCommands(List.of(new CommandExecutionSpecification(LuxomAction.CLEAR.getCommand() + ",0," + getAddress())));
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler.config;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* {@link LuxomBridgeConfig} is the general config class for Luxom Bridges.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomBridgeConfig {
|
||||
public @Nullable String ipAddress;
|
||||
public int port;
|
||||
|
||||
/**
|
||||
* reconnect after X minutes when disconnected
|
||||
*/
|
||||
public int reconnectInterval;
|
||||
public int aliveCheckInterval;
|
||||
|
||||
/**
|
||||
* if true, on communication error the devices will NOT go offline...
|
||||
* if false, they will go offline. In both instances they will get (re)pinged after reconnect.
|
||||
*
|
||||
*/
|
||||
public boolean useFastReconnect = false;
|
||||
|
||||
public boolean sameConnectionParameters(LuxomBridgeConfig config) {
|
||||
return Objects.equals(ipAddress, config.ipAddress) && config.port == port
|
||||
&& (reconnectInterval == config.reconnectInterval) && (aliveCheckInterval == config.aliveCheckInterval);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link LuxomThingConfig} is the general config class for luxom things.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomThingConfig {
|
||||
public Boolean doesNotReply = Boolean.FALSE;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* {@link LuxomThingDimmerConfig} is the config class for Niko Home Control Dimmer Actions.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomThingDimmerConfig extends LuxomThingConfig {
|
||||
private static final int DEFAULT_ONLEVEL = 100;
|
||||
|
||||
public BigDecimal onLevel = new BigDecimal(DEFAULT_ONLEVEL);
|
||||
public Boolean onToLast = Boolean.FALSE;
|
||||
public Integer stepPercentage = 5;
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.handler.util;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* converts the hexadecimal string representation to a integer value between 0 - 100
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PercentageConverter {
|
||||
/**
|
||||
* @param hexRepresentation
|
||||
* @return if hexRepresentation == null return -1, otherwise return percentage
|
||||
*/
|
||||
public static int getPercentage(@Nullable String hexRepresentation) {
|
||||
if (hexRepresentation == null)
|
||||
return -1;
|
||||
int decimal = Integer.parseInt(hexRepresentation, 16);
|
||||
BigDecimal level = new BigDecimal(100 * decimal).divide(new BigDecimal(255), RoundingMode.FLOOR);
|
||||
return level.intValue();
|
||||
}
|
||||
|
||||
public static String getHexRepresentation(int percentage) {
|
||||
BigDecimal decimal = new BigDecimal(255 * percentage).divide(new BigDecimal(100), RoundingMode.CEILING);
|
||||
return Integer.toHexString(decimal.intValue()).toUpperCase();
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* luxom action
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum LuxomAction {
|
||||
HEARTBEAT("*U", false),
|
||||
ACKNOWLEDGE("@1*V", false),
|
||||
TOGGLE("*T", true),
|
||||
PING("*P", true),
|
||||
MODULE_INFORMATION("*!", false),
|
||||
PASSWORD_REQUEST("@1*PW-", false),
|
||||
CLEAR_RESPONSE("@1*C", true),
|
||||
SET_RESPONSE("@1*S", true),
|
||||
DATA_RESPONSE("@1*A", true, true),
|
||||
DATA_BYTE_RESPONSE("@1*Z", false),
|
||||
DATA("*A", true, true),
|
||||
DATA_BYTE("*Z", false),
|
||||
SET("*S", true),
|
||||
CLEAR("*C", true),
|
||||
REQUEST_FOR_INFORMATION("*?", false),
|
||||
INVALID_ACTION("-INVALID-", false); // this is not part of the luxom api, it's for internal use.;
|
||||
|
||||
private final String command;
|
||||
private final boolean hasAddress;
|
||||
private final boolean needsData;
|
||||
|
||||
LuxomAction(String command, boolean hasAddress) {
|
||||
this(command, hasAddress, false);
|
||||
}
|
||||
|
||||
LuxomAction(String command, boolean hasAddress, boolean needsData) {
|
||||
this.command = command;
|
||||
this.hasAddress = hasAddress;
|
||||
this.needsData = needsData;
|
||||
}
|
||||
|
||||
public static LuxomAction of(String command) {
|
||||
return Arrays.stream(LuxomAction.values()).filter(a -> a.getCommand().equals(command)).findFirst()
|
||||
.orElse(INVALID_ACTION);
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public boolean isHasAddress() {
|
||||
return hasAddress;
|
||||
}
|
||||
|
||||
public boolean isNeedsData() {
|
||||
return needsData;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* luxom command
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomCommand {
|
||||
private final LuxomAction action;
|
||||
private final @Nullable String address; // must for data byte commands be set after construction
|
||||
|
||||
private @Nullable String data;
|
||||
|
||||
public LuxomCommand(String command) {
|
||||
if (command.length() == 0) {
|
||||
action = LuxomAction.INVALID_ACTION;
|
||||
data = command;
|
||||
address = null;
|
||||
return;
|
||||
}
|
||||
String[] parts = command.split(",");
|
||||
|
||||
if (parts.length == 1) {
|
||||
if (command.startsWith(LuxomAction.MODULE_INFORMATION.getCommand())) {
|
||||
action = LuxomAction.MODULE_INFORMATION;
|
||||
data = command.substring(2);
|
||||
} else if (command.equals(LuxomAction.PASSWORD_REQUEST.getCommand())) {
|
||||
action = LuxomAction.PASSWORD_REQUEST;
|
||||
data = null;
|
||||
} else if (command.equals(LuxomAction.ACKNOWLEDGE.getCommand())) {
|
||||
action = LuxomAction.ACKNOWLEDGE;
|
||||
data = null;
|
||||
} else {
|
||||
action = LuxomAction.INVALID_ACTION;
|
||||
data = command;
|
||||
}
|
||||
address = null;
|
||||
} else {
|
||||
action = LuxomAction.of(parts[0]);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (action.isHasAddress()) {
|
||||
// first 0 not needed ?
|
||||
for (int i = 2; i < parts.length; i++) {
|
||||
stringBuilder.append(parts[i]);
|
||||
if (i != (parts.length - 1)) {
|
||||
stringBuilder.append(",");
|
||||
}
|
||||
}
|
||||
address = stringBuilder.toString();
|
||||
data = null;
|
||||
} else {
|
||||
for (int i = 1; i < parts.length; i++) {
|
||||
stringBuilder.append(parts[i]);
|
||||
if (i != (parts.length - 1)) {
|
||||
stringBuilder.append(",");
|
||||
}
|
||||
}
|
||||
address = null;
|
||||
data = stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LuxomCommand{" + "action=" + action + ", address='" + address + '\'' + ", data='" + data + '\'' + '}';
|
||||
}
|
||||
|
||||
public LuxomAction getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setData(@Nullable String data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.luxom.internal.handler.LuxomBridgeHandler;
|
||||
import org.openhab.binding.luxom.internal.handler.LuxomConnectionException;
|
||||
import org.openhab.binding.luxom.internal.handler.config.LuxomBridgeConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LuxomCommunication} class is able to do the following tasks with Luxom IP
|
||||
* systems:
|
||||
* <ul>
|
||||
* <li>Start and stop TCP socket connection with Luxom IP-interface.
|
||||
* <li>Read all setup and status information from the Luxom Controller.
|
||||
* <li>Execute Luxom commands.
|
||||
* <li>Listen to events from Luxom.
|
||||
* </ul>
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LuxomCommunication {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LuxomCommunication.class);
|
||||
|
||||
private final LuxomBridgeHandler bridgeHandler;
|
||||
|
||||
private @Nullable Socket luxomSocket;
|
||||
private @Nullable PrintWriter luxomOut;
|
||||
private @Nullable BufferedReader luxomIn;
|
||||
|
||||
private volatile boolean listenerStopped;
|
||||
private volatile boolean stillListeningToEvents;
|
||||
|
||||
public LuxomCommunication(LuxomBridgeHandler luxomBridgeHandler) {
|
||||
super();
|
||||
bridgeHandler = luxomBridgeHandler;
|
||||
}
|
||||
|
||||
public synchronized void startCommunication() throws LuxomConnectionException {
|
||||
try {
|
||||
waitForEventListenerThreadToStop();
|
||||
|
||||
initializeSocket();
|
||||
|
||||
// Start Luxom event listener. This listener will act on all messages coming from
|
||||
// IP-interface.
|
||||
(new Thread(this::runLuxomEvents,
|
||||
"OH-binding-" + bridgeHandler.getThing().getBridgeUID() + "-listen-for-events")).start();
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new LuxomConnectionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForEventListenerThreadToStop() throws InterruptedException, IOException {
|
||||
for (int i = 1; stillListeningToEvents && (i <= 5); i++) {
|
||||
// the events listener thread did not finish yet, so wait max 5000ms before restarting
|
||||
// noinspection BusyWait
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
if (stillListeningToEvents) {
|
||||
throw new IOException("starting but previous connection still active after 5000ms");
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeSocket() throws IOException {
|
||||
LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
|
||||
if (luxomBridgeConfig != null) {
|
||||
InetAddress addr = InetAddress.getByName(luxomBridgeConfig.ipAddress);
|
||||
int port = luxomBridgeConfig.port;
|
||||
|
||||
luxomSocket = new Socket(addr, port);
|
||||
luxomSocket.setReuseAddress(true);
|
||||
luxomSocket.setKeepAlive(true);
|
||||
luxomOut = new PrintWriter(luxomSocket.getOutputStream());
|
||||
luxomIn = new BufferedReader(new InputStreamReader(luxomSocket.getInputStream()));
|
||||
logger.debug("Luxom: connected via local port {}", luxomSocket.getLocalPort());
|
||||
} else {
|
||||
logger.warn("Luxom: ip bridge not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup socket when the communication with Luxom IP-interface is closed.
|
||||
*/
|
||||
public synchronized void stopCommunication() {
|
||||
listenerStopped = true;
|
||||
|
||||
closeSocket();
|
||||
}
|
||||
|
||||
private void closeSocket() {
|
||||
if (luxomSocket != null) {
|
||||
try {
|
||||
luxomSocket.close();
|
||||
} catch (IOException ignore) {
|
||||
// ignore IO Error when trying to close the socket if the intention is to close it anyway
|
||||
}
|
||||
}
|
||||
luxomSocket = null;
|
||||
|
||||
logger.debug("Luxom: communication stopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that handles inbound communication from Luxom, to be called on a separate thread.
|
||||
* <p>
|
||||
* The thread listens to the TCP socket opened at instantiation of the {@link LuxomCommunication} class
|
||||
* and interprets all inbound json messages. It triggers state updates for active channels linked to the Niko Home
|
||||
* Control actions. It is started after initialization of the communication.
|
||||
*/
|
||||
private void runLuxomEvents() {
|
||||
StringBuilder luxomMessage = new StringBuilder();
|
||||
|
||||
logger.debug("Luxom: listening for events");
|
||||
listenerStopped = false;
|
||||
stillListeningToEvents = true;
|
||||
|
||||
try {
|
||||
boolean mayUseFastReconnect = false;
|
||||
boolean mustDoFullReconnect = false;
|
||||
while (!listenerStopped && (luxomIn != null)) {
|
||||
int nextChar = luxomIn.read();
|
||||
if (nextChar == -1) {
|
||||
logger.trace("Luxom: stream ends unexpectedly...");
|
||||
LuxomBridgeConfig luxomBridgeConfig = bridgeHandler.getIPBridgeConfig();
|
||||
if (mayUseFastReconnect && luxomBridgeConfig != null && luxomBridgeConfig.useFastReconnect) {
|
||||
// we stay in the loop and just reinitialize socket
|
||||
mayUseFastReconnect = false; // just once use fast reconnect
|
||||
this.closeSocket();
|
||||
this.initializeSocket();
|
||||
// followed by forced update of status
|
||||
bridgeHandler.forceRefreshThings();
|
||||
} else {
|
||||
listenerStopped = true;
|
||||
mustDoFullReconnect = true;
|
||||
}
|
||||
} else {
|
||||
mayUseFastReconnect = true; // reset
|
||||
char c = (char) nextChar;
|
||||
logger.trace("Luxom: read char {}", c);
|
||||
|
||||
luxomMessage.append(c);
|
||||
|
||||
if (';' == c) {
|
||||
String message = luxomMessage.toString();
|
||||
bridgeHandler.handleIncomingLuxomMessage(message.substring(0, message.length() - 1));
|
||||
luxomMessage = new StringBuilder();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mustDoFullReconnect) {
|
||||
// I want to do this out of the loop
|
||||
bridgeHandler.reconnect();
|
||||
}
|
||||
logger.trace("Luxom: stopped listening to events");
|
||||
} catch (IOException e) {
|
||||
logger.warn("Luxom: listening to events - IO exception", e);
|
||||
if (!listenerStopped) {
|
||||
stillListeningToEvents = false;
|
||||
// this is a socket error, not a communication stop triggered from outside this runnable
|
||||
// the IO has stopped working, so we need to close cleanly and try to restart
|
||||
bridgeHandler.handleCommunicationError(e);
|
||||
return;
|
||||
}
|
||||
} finally {
|
||||
stillListeningToEvents = false;
|
||||
}
|
||||
|
||||
// this is a stop from outside the runnable, so just log it and stop
|
||||
logger.debug("Luxom: event listener thread stopped");
|
||||
}
|
||||
|
||||
public synchronized void sendMessage(String message) throws IOException {
|
||||
logger.debug("Luxom: send {}", message);
|
||||
if (luxomOut != null) {
|
||||
luxomOut.print(message + ";");
|
||||
luxomOut.flush();
|
||||
if (luxomOut.checkError()) {
|
||||
throw new IOException(String.format("luxom communication error when sending message: %s", message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return luxomSocket != null && luxomSocket.isConnected();
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link LuxomSystemInfo} class represents the systeminfo Luxom communication object. It contains all
|
||||
* Luxom system data received from the Luxom IP controller when initializing the connection.
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class LuxomSystemInfo {
|
||||
|
||||
private @Nullable String swVersion = "";
|
||||
|
||||
public @Nullable String getSwVersion() {
|
||||
return swVersion;
|
||||
}
|
||||
|
||||
public void setSwVersion(@Nullable String swVersion) {
|
||||
this.swVersion = swVersion;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="luxom" 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>Luxom Binding</name>
|
||||
<description>This is the binding for Luxom bus system (https://www.luxom.io/)</description>
|
||||
</binding:binding>
|
@ -0,0 +1,38 @@
|
||||
# binding
|
||||
|
||||
binding.luxom.name = Luxom Binding
|
||||
binding.luxom.description = This is the binding for Luxom bus system (https://www.luxom.io/)
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.luxom.switch.label = Switch
|
||||
thing-type.luxom.switch.description = Switch type action in Luxom
|
||||
thing-type.luxom.dimmer.label = Dimmer
|
||||
thing-type.luxom.dimmer.description = Dimmer type action in Luxom
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.luxom.switch.address.label = Address
|
||||
thing-type.config.luxom.switch.address.description = Luxom bus address
|
||||
thing-type.config.luxom.dimmer.address.label = Address
|
||||
thing-type.config.luxom.dimmer.address.description = Luxom bus address
|
||||
thing-type.config.luxom.dimmer.onLevel.label = On Level
|
||||
thing-type.config.luxom.dimmer.onLevel.description = Output level to go to when an ON command is received. Default is 100%.
|
||||
thing-type.config.luxom.dimmer.onToLast.label = Turn On To Last Level
|
||||
thing-type.config.luxom.dimmer.onToLast.description = If set to true, dimmer will go to the last non-zero level set when an ON command is received. If the last level cannot be determined, the value of onLevel will be used instead.
|
||||
thing-type.config.luxom.dimmer.stepPercentage.label = Step Value
|
||||
thing-type.config.luxom.dimmer.stepPercentage.description = Step value used for increase/decrease of dimmer brightness, default 5%
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.luxom.button.label = Button
|
||||
channel-type.luxom.button.description = Pushbutton control for action in Luxom
|
||||
|
||||
# messages
|
||||
status.awaiting-initial-response = Awaiting initial response
|
||||
status.bridge-configuration-missing = Bridge configuration missing
|
||||
status.connecting = Connecting
|
||||
status.bridge-address-missing = Bridge address not specified
|
||||
status.bridge-initializing = Luxom bridge is initializing
|
||||
status.bridge-handler-missing = No bridge associated
|
||||
status.thing-address-missing = Address is missing
|
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="luxom"
|
||||
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="bridge">
|
||||
<label>Luxom Bridge</label>
|
||||
<description>This bridge represents a Luxom IP-interface (for example a DS-65L)</description>
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>IP or Host Name</label>
|
||||
<description>The IP or host name of the Luxom IP-interface</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer" required="true">
|
||||
<label>Bridge Port</label>
|
||||
<description>Port to communicate with Luxom IP-interface, default 2300</description>
|
||||
<default>2300</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="reconnectInterval" type="integer" min="1" max="60" unit="min">
|
||||
<label>Reconnect Interval</label>
|
||||
<description>The period in minutes that the handler will wait between connection attempts after disconnect</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>1</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<thing-type id="switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>switch</label>
|
||||
<description>Luxom Switch</description>
|
||||
<channels>
|
||||
<channel id="switch" typeId="switchState"/>
|
||||
</channels>
|
||||
<representation-property>address</representation-property>
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>Luxom bus address</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<thing-type id="dimmer">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="bridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Dimmer</label>
|
||||
<description>Luxom Dimmer</description>
|
||||
<channels>
|
||||
<channel id="brightness" typeId="system.brightness"/>
|
||||
</channels>
|
||||
<config-description>
|
||||
<parameter name="address" type="text" required="true">
|
||||
<label>Address</label>
|
||||
<description>Luxom bus address</description>
|
||||
<advanced>false</advanced>
|
||||
</parameter>
|
||||
<parameter name="onLevel" type="decimal" min="0.01" max="100.00">
|
||||
<label>On Level</label>
|
||||
<description>Output level to go to when an ON command is received. Default is 100%.</description>
|
||||
<default>100</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="onToLast" type="boolean">
|
||||
<label>Turn On To Last Level</label>
|
||||
<description>
|
||||
If set to true, dimmer will go to the last non-zero level set when an ON command is received. If the
|
||||
last level cannot be determined, the value of onLevel will be used instead.
|
||||
</description>
|
||||
<default>false</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="stepPercentage" type="integer" required="false">
|
||||
<label>Step Value</label>
|
||||
<description>Step value used for increase/decrease of dimmer brightness, default 5%</description>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="switchState">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Switch</label>
|
||||
<description>Switch control for action in Luxom</description>
|
||||
<category>Switch</category>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class LuxomCommandTest {
|
||||
|
||||
@Test
|
||||
void parsePulseCommand() {
|
||||
LuxomCommand command = new LuxomCommand("*P,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.PING, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parsePasswordRequest() {
|
||||
LuxomCommand command = new LuxomCommand(LuxomAction.PASSWORD_REQUEST.getCommand());
|
||||
|
||||
assertEquals(LuxomAction.PASSWORD_REQUEST, command.getAction());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseClearCommand() {
|
||||
LuxomCommand command = new LuxomCommand("*C,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.CLEAR, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseClearResponse() {
|
||||
LuxomCommand command = new LuxomCommand("@1*C,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.CLEAR_RESPONSE, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseClearResponse2() {
|
||||
LuxomCommand command = new LuxomCommand("@1*C,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.CLEAR_RESPONSE, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseSetCommand() {
|
||||
LuxomCommand command = new LuxomCommand("*S,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.SET, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseSetResponse() {
|
||||
LuxomCommand command = new LuxomCommand("@1*S,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.SET_RESPONSE, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseDimCommand() {
|
||||
LuxomCommand command = new LuxomCommand("*A,0,1,04");
|
||||
|
||||
assertEquals(LuxomAction.DATA, command.getAction());
|
||||
assertEquals("1,04", command.getAddress());
|
||||
assertNull(command.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseDataCommand() {
|
||||
LuxomCommand command = new LuxomCommand("*Z,048");
|
||||
|
||||
assertEquals(LuxomAction.DATA_BYTE, command.getAction());
|
||||
assertEquals("048", command.getData());
|
||||
assertNull(command.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseDataResponseCommand() {
|
||||
LuxomCommand command = new LuxomCommand("@1*Z,048");
|
||||
|
||||
assertEquals(LuxomAction.DATA_BYTE_RESPONSE, command.getAction());
|
||||
assertEquals("048", command.getData());
|
||||
assertNull(command.getAddress());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 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.luxom.internal.protocol;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.binding.luxom.internal.handler.util.PercentageConverter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Kris Jespers - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class PercentageConverterTest {
|
||||
@Test
|
||||
void hexToPercentage() {
|
||||
assertEquals(34, PercentageConverter.getPercentage("057"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hexToPercentage100() {
|
||||
assertEquals(100, PercentageConverter.getPercentage("0FF"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void percentageToHex() {
|
||||
assertEquals("57", PercentageConverter.getHexRepresentation(34));
|
||||
}
|
||||
}
|
@ -201,6 +201,7 @@
|
||||
<module>org.openhab.binding.loxone</module>
|
||||
<module>org.openhab.binding.luftdateninfo</module>
|
||||
<module>org.openhab.binding.lutron</module>
|
||||
<module>org.openhab.binding.luxom</module>
|
||||
<module>org.openhab.binding.luxtronikheatpump</module>
|
||||
<module>org.openhab.binding.magentatv</module>
|
||||
<module>org.openhab.binding.mail</module>
|
||||
|
Loading…
Reference in New Issue
Block a user