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,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.nikohomecontrol</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View 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

View File

@@ -0,0 +1,348 @@
# Niko Home Control Binding
**Upgrade notice for Niko Home Control II and openHAB 2.5.3**:
Starting with openHAB 2.5.3 the binding uses Niko Home Control hobby API token based authentication.
The Niko Home Control hobby API is available with Niko Home Control system version 2.5.1 or newer.
If currently using a profile and password based authentication with the binding (upgrading from an openHAB version before 2.5.3), you will need to start using hobby API token based authentication.
Make sure your Niko Home Control system is at version 2.5.1 or newer.
Request a hobby API token at [mynikohomecontrol](https://mynikohomecontrol.niko.eu).
In the bridge configuration, put the received token in the API Token parameter.
Delete the values for Bridge Port and Profile parameters.
The Niko Home Control binding integrates with a [Niko Home Control](https://www.niko.eu/) system through a Niko Home Control IP-interface or Niko Home Control Connected Controller.
The binding supports both Niko Home Control I and Niko Home Control II.
For Niko Home Control I, the binding has been tested with a Niko Home Control IP-interface (550-00508).
This IP-interface provides access on the LAN.
The binding does not require a Niko Home Control Gateway (550-00580), but does work with it in the LAN.
It has also been confirmed to work with the Niko Home Control Connected Controller (550-00003) in a Niko Home Control I installation.
For Niko Home Control II, the binding requires the Niko Home Control Connected Controller (550-00003) or Niko Home Control Wireless Smart Hub (552-00001).
The installation only needs to be 'connected' (registered on the Niko Home Control website) when first connecting to validate the authentication, and will work strictly in the LAN thereafter.
For Niko Home Control I, the binding exposes all actions from the Niko Home Control System that can be triggered from the smartphone/tablet interface, as defined in the Niko Home Control I programming software.
For Niko Home Control II, the binding exposes all devices in the system.
Supported device types are switches, dimmers and rollershutters or blinds, thermostats and energy meters (Niko Home Control II only).
Niko Home Control alarm and notice messages are retrieved and made available in the binding.
## Supported Things
The Niko Home Control Controller is represented as a bridge in the binding.
Connected to a bridge, the Niko Home Control Binding supports alloff actions, on/off actions (e.g. for lights or groups of lights), dimmers, rollershutters or blinds, thermostats and energy meters (only Niko Home Control II).
## Binding Configuration
The bridge representing the Niko Home Control IP-interface needs to be added first in the things file or through Paper UI.
A bridge can be auto-discovered or created manually.
An auto-discovered bridge will have an IP-address parameter automatically filled with the current IP-address of the IP-interface.
This IP-address for the discovered bridge will automatically update when the IP-address of the IP-interface changes.
The IP-address and port can be set when manually creating the bridge.
If the IP-address is set on a manually created bridge, no attempt will be made to discover the correct IP-address.
You are responsible to force a fixed IP address on the Niko Home Control IP-interface through settings in your DHCP server.
For Niko Home Control I, the port is set to 8000 by default and should match the port used by the Niko Home Control I IP-interface or Niko Home Control I Connected Controller.
For Niko Home Control II, the port is set to 8884 by default and should match the secure MQTT port used by the Niko Home Control II Connected Controller.
For Niko Home Control I, no further bridge configuration is required when using auto-discovery.
The Niko Home Control II bridge has an extra required parameter for the API token.
The API token can be retrieved from your Niko Home Control profile page on the Niko Home Control website.
For that, you will need to add the Hobby API as a connected service on your profile.
Note that the API token is only valid for one year after creation.
The token expiry date is visible in the bridge properties.
Entries will also be written in the log when the token is about to expire (starting 14 days in advance).
An optional refresh interval will be used to restart the bridge at regular intervals (every 300 minutes by default).
Restarting the bridge at regular times improves the connection stability and avoids loss of connection.
It can be turned off completely by setting the parameter to 0.
## Discovery
A discovery scan will first discover the Niko Home Control IP-interface or Niko Home Control Connected Controller in the network as a bridge.
Default parameters will be used.
Note that this may fail to find the correct Niko Home Control IP-interface when there are multiple IP-interfaces in the network, or when traffic to port 10000 on the openHAB server is blocked.
When the Niko Home Control bridge is added as a thing, from the discovery inbox or manually, system information will be read from the Niko Home Control Controller and will be put in the bridge properties, visible through Paper UI.
Subsequently, all defined actions that can be triggered from a smartphone/tablet in the Niko Home Control I system, respectively all actions in the Niko Home Control II system, will be discovered and put in the inbox.
It is possible to trigger a manual scan for things on the Niko Home Control bridge.
Note that Niko Home Control II will require the token to be set on the bridge before the scan for actions can succeed.
The bridge will remain offline as long as these parameters are not set.
If the Niko Home Control system has locations configured, these will be copied to thing locations and grouped as such in Paper UI.
Locations can subsequently be changed through the thing location parameter in Paper UI.
## Thing Configuration
Besides using Paper UI to manually configure things or adding automatically discovered things through Paper UI, you can add thing definitions in the things file.
The Thing configuration for the **bridge** uses the following syntax:
For Niko Home Control I:
```
Bridge nikohomecontrol:bridge:<bridgeId> [ addr="<IP-address of IP-interface>", port=<listening port>,
refresh=<Refresh interval> ]
```
`bridgeId` can have any value.
`addr` is the fixed Niko Home Control IP-interface or Connected Controller address and is required.
`port` will be the port used to connect and is 8000 by default.
`refresh` is the interval to restart the communication in minutes (300 by default), if 0 or omitted the connection will not restart at regular intervals.
For Niko Home Control II:
```
Bridge nikohomecontrol:bridge2:<bridgeId> [ addr="<IP-address of IP-interface>", port=<listening port>, profile="<profile>",
password="<token>", refresh=<Refresh interval> ]
```
`bridgeId` can have any value.
`addr` is the fixed Niko Home Connected Controller address and is required.
`port` will be the port used to connect and is 8884 by default.
`profile` is the profile UUID being used, hobby by default.
`password` is the API token retrieved from the Niko Home Control website, cannot be empty.
`refresh` is the interval to restart the communication in minutes (300 by default), if 0 or omitted the connection will not restart at regular intervals.
Advanced configuration note for Niko Home Control II:
It is possible to use authentication based on a touch panel profile, bypassing the hobby API token authentication.
To make this work, you have to define a password protected touch profile in the Niko Home Control programming software.
Extract the embedded SQLite database from the configuration file.
Look for the profile you created in the `Profile` table (using a SQLite database browser tool) and copy the `CreationId` into the profile parameter for the bridge.
The port parameter on the bridge has to be set to 8883.
The API token parameter should be set to the profile password.
The Thing configuration for **Niko Home Control actions** has the following syntax:
```
Thing nikohomecontrol:<thing type>:<bridgeId>:<thingId> "Label" @ "Location"
[ actionId="<Niko Home Control action ID>",
step=<dimmer increase/decrease step value> ]
```
or nested in the bridge configuration:
```
<thing type> <thingId> "Label" @ "Location" [ actionId="<Niko Home Control action ID>",
step=<dimmer increase/decrease step value> ]
```
The following action thing types are valid for the configuration:
```
pushButton, onOff, dimmer, blind
```
`pushButton` types are used to map directly to stateless actions in the Niko Home Control system, such as All Off actions.
Discovery will identify All Off actions and map them to `pushButton` things.
`thingId` can have any value, but will be set to the same value as the actionId parameter if discovery is used.
`"Label"` is an optional label for the thing.
`@ "Location"` is optional, and represents the location of the Thing. Auto-discovery would have assigned a value automatically.
For Niko Home Control I, the `actionId` parameter is the unique IP Interface Object ID (`ipInterfaceObjectId`) as automatically assigned in the Niko Home Control Controller when programming the Niko Home Control system using the Niko Home Control I programming software.
It is not directly visible in the Niko Home Control programming or user software, but will be detected and automatically set by openHAB discovery.
For textual configuration, you can manually retrieve it from the content of the .nhcp configuration file created by the programming software.
Open the file with an unzip tool to read its content.
For Niko Home Control II, the `actionId` parameter is a unique ID for the action in the controller.
It can only be auto-discovered.
If you want to define the action through textual configuration, the easiest way is to first do discovery on the bridge to get the correct `actionId` to use in the textual configuration.
Discover and add the thing you want to add.
Note down the `actionId` parameter from the thing, remove it before adding it again through textual configuration, with the same `actionId` parameter.
Alternatively the `actionId` can be retrieved from the configuration file.
The file contains a SQLLite database.
The database contains a table `Action` with column `FifthplayId` corresponding to the required `actionId` parameter.
The `step` parameter is only available for dimmers.
It sets a step value for dimmer increase/decrease actions.
The parameter is optional and set to 10 by default.
The Thing configuration for **Niko Home Control thermostats** has the following syntax:
```
Thing nikohomecontrol:thermostat:<bridgeId>:<thingId> "Label" @ "Location"
[ thermostatId="<Niko Home Control thermostat ID>",
overruleTime=<default duration for overrule temperature in minutes> ]
```
or nested in the bridge configuration:
```
thermostat <thingId> "Label" @ "Location" [ thermostatId="<Niko Home Control thermostat ID>" ]
```
`thingId` can have any value, but will be set to the same value as the thermostatId parameter if discovery is used.
`"Label"` is an optional label for the Thing.
`@ "Location"` is optional, and represents the location of the thing.
Auto-discovery would have assigned a value automatically.
The `thermostatId` parameter is the unique IP Interface Object ID as automatically assigned in the Niko Home Control I Controller when programming the Niko Home Control system using the Niko Home Control programming software.
It is not directly visible in the Niko Home Control programming or user software, but will be detected and automatically set by openHAB discovery.
For textual configuration, it can be retrieved from the .nhcp configuration file.
For Niko Home Control II, the `thermostatId` parameter is a unique ID for the thermostat in the controller.
It can only be auto-discovered.
If you want to define the thermostat through textual configuration, you may first need to do discovery on the bridge to get the correct `thermostatId` to use in the textual configuration.
The `overruleTime` parameter is used to set the standard overrule duration in minutes when you set a new setpoint without providing an overrule duration.
The default value is 60 minutes.
The Thing configuration for **Niko Home Control energy meters** has the following syntax:
```
Thing nikohomecontrol:energymeter:<bridgeId>:<thingId> "Label" @ "Location"
[ energyMeterId="<Niko Home Control energy meter ID>" ]
```
or nested in the bridge configuration:
```
energymeter <thingId> "Label" @ "Location" [ energyMeterId="<Niko Home Control energy meter ID>" ]
```
`thingId` can have any value, but will be set to the same value as the thermostatId parameter if discovery is used.
`"Label"` is an optional label for the Thing.
`@ "Location"` is optional, and represents the location of the thing. Auto-discovery would have assigned a value automatically.
Energy meters can only be configured for Niko Home Control II.
The `energyMeterId` parameter is a unique ID for the energy meter in the controller.
It can only be auto-discovered.
If you want to define the energy meter through textual configuration, you may first need to do discovery on the bridge to get the correct `energyMeterId` to use in the textual configuration.
## Channels
For thing type `pushButton` the supported channel is `button`.
OnOff command types are supported.
For this channel, `autoupdate="false"` is set by default.
This will stop the linked item state from updating.
For thing type `onOff` the supported channel is `switch`.
OnOff command types are supported.
For thing type `dimmer` the supported channel is `brightness`.
OnOff, IncreaseDecrease and Percent command types are supported.
Note that sending an ON command will switch the dimmer to the value stored when last turning the dimmer off, or 100% depending on the configuration in the Niko Home Control Controller.
This can be changed with the Niko Home Control programming software.
For thing type `blind` the supported channel is `rollershutter`. UpDown, StopMove and Percent command types are supported.
For thing type `thermostat` the supported channels are `measured`, `mode`, `setpoint`, `overruletime` and `demand`.
`measured` gives the current temperature in QuantityType<Temperature>, allowing for different temperature units.
This channel is read only.
`mode` can be set and shows the current thermostat mode.
Allowed values are 0 (day), 1 (night), 2 (eco), 3 (off), 4 (cool), 5 (prog 1), 6 (prog 2), 7 (prog 3).
If mode is set, the `setpoint` temperature will return to its standard value from the mode.
`setpoint` can be set and shows the current thermostat setpoint value in QuantityType<Temperature>.
When updating `setpoint`, it will overrule the temperature setpoint defined by the thermostat mode for `overruletime` duration.
`overruletime` is used to set the total duration to apply the setpoint temperature set in the setpoint channel before the thermostat returns to the setting in its mode.
`demand` is a number indicating of the system is actively heating/cooling.
The value will be 1 for heating, -1 for cooling and 0 if not heating or cooling.
Note that cooling in NHC I is set by the binding, and will incorrectly show cooling demand when the system does not have cooling capabilities.
In NHC II, `measured` and `setpoint` temperatures will always report in 0.5°C increments due to a Niko Home Control II API restriction.
For thing type `energymeter` the only supported channel is `power`.
This channel is read only and give a current power consumption/production reading (positive for consumption) every 2 seconds.
The bridge has two trigger channels `alarm` and `notice`.
It can be used as a trigger to rules.
The event message is the alarm or notice text coming from Niko Home Control.
## Limitations
The binding has been tested with a Niko Home Control I IP-interface (550-00508) and the Niko Home Control Connected Controller (550-00003) for Niko Home Control I and II, and the Niko Home Control Wireless Smart Hub for Niko Home Control II.
The action events implemented are limited to onOff, dimmer, allOff, scenes, PIR and rollershutter or blinds.
Other actions have not been implemented.
It is not possible to tilt the slates of venetian blinds.
Beyond action, thermostat and electricity usage events, the Niko Home Control communication also supports gas and water usage data.
Niko Home Control II also supports 3rd party devices.
All of this has not been implemented.
Electricity power consumption/production has only been implemented for Niko Home Control II.
## Example
.things:
```
Bridge nikohomecontrol:bridge:nhc1 [ addr="192.168.0.70", port=8000, refresh=300 ] {
pushButton 1 "AllOff" [ actionId="1" ]
onOff 2 "LivingRoom" @ "Downstairs" [ actionId="2" ]
dimmer 3 "TVRoom" [ actionId="3", step=5 ]
blind 4 [ actionId="4" ]
thermostat 5 [ thermostatId="0", overruleTime=10 ]
}
Bridge nikohomecontrol:bridge2:nhc2 [ addr="192.168.0.70", port=8884, password="A.B.C", refresh=300 ] {
pushButton 1 "AllOff" [ actionId="12345678-abcd-1234-ef01-aa12bb34ee89" ]
onOff 2 "Office" @ "Downstairs" [ actionId="12345678-abcd-1234-ef01-aa12bb34cc56" ]
dimmer 3 "DiningRoom" [ actionId="abcdef01-abcd-1234-ab98-abcdef012345", step=5 ]
blind 4 [ actionId="abcdef01-abcd-1234-ab98-abcdefabcdef" ]
thermostat 5 [ thermostatId="abcdef01-abcd-1234-ab98-012345abcdef", overruleTime=10 ]
electricitymeter 6 [ energyMeterId="abcdef01-abcd-1234-cd56-ffee34567890" ]
}
Bridge nikohomecontrol:bridge:nhc3 [ addr="192.168.0.110" ] {
onOff 11 @ "Upstairs" [ actionId="11" ]
dimmer 12 [ actionId="12", step=5 ]
blind 13 [ actionId="13" ]
thermostat 14 [ thermostatId="10" ]
}
```
.items:
```
Switch AllOff {channel="nikohomecontrol:onOff:nhc1:1:button"} # Pushbutton for All Off action
Switch LivingRoom {channel="nikohomecontrol:onOff:nhc1:2:switch"} # Switch for onOff type action
Dimmer TVRoom {channel="nikohomecontrol:dimmer:nhc1:3:brightness"} # Changing brightness dimmer type action
Rollershutter Kitchen {channel="nikohomecontrol:blind:nhc1:4:rollershutter"} # Controlling rollershutter or blind type action
Number:Temperature CurTemperature "[%.1f °F]" {channel="nikohomecontrol:thermostat:nhc1:5:measured"} # Getting measured temperature from thermostat in °F, read only
Number ThermostatMode {channel="nikohomecontrol:thermostat:nhc1:5:mode"} # Get and set thermostat mode
Number:Temperature SetTemperature "[%.1f °C]" {channel="nikohomecontrol:thermostat:nhc1:5:setpoint"} # Get and set target temperature in °C
Number OverruleDuration {channel="nikohomecontrol:thermostat:nhc1:5:overruletime"} # Get and set the overrule time
Number ThermostatDemand {channel="nikohomecontrol:thermostat:nhc1:5:demand"} # Get the current heating/cooling demand
Number:Power CurPower "[%.0f W]" {channel="nikohomecontrol:energyMeter:nhc2:6:power"} # Get current power consumption
```
.sitemap:
```
Switch item=AllOff
Switch item=LivingRoom
Slider item=TVRoom
Switch item=TVRoom # allows switching dimmer item off or on (with controller defined behavior)
Rollershutter item=Kitchen
Text item=CurTemperature
Selection item=ThermostatMode mappings=[0="day", 1="night", 2="eco", 3="off", 4="cool", 5="prog 1", 6="prog 2", 7="prog 3"]
Setpoint item=SetTemperature minValue=0 maxValue=30
Slider item=OverruleDuration minValue=0 maxValue=120
Text item=Power
```
Example trigger rule:
```
rule "example trigger rule"
when
Channel 'nikohomecontrol:bridge:nhc1:alarm' triggered or
Channel 'nikohomecontrol:bridge2:nhc2:notice' triggered
then
var message = receivedEvent.getEvent()
logInfo("nhcTriggerExample", "Message: {}", message)
...
end
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.nikohomecontrol</artifactId>
<name>openHAB Add-ons :: Bundles :: Niko Home Control Binding</name>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.nikohomecontrol-${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-nikohomecontrol" description="Niko Home Control Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mqtt</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.nikohomecontrol/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,90 @@
/**
* 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.nikohomecontrol.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link NikoHomeControlBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBindingConstants {
public static final String BINDING_ID = "nikohomecontrol";
// List of all Thing Type UIDs
// bridge
public static final ThingTypeUID BRIDGEI_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge");
public static final ThingTypeUID BRIDGEII_THING_TYPE = new ThingTypeUID(BINDING_ID, "bridge2");
// generic thing types
public static final ThingTypeUID THING_TYPE_PUSHBUTTON = new ThingTypeUID(BINDING_ID, "pushButton");
public static final ThingTypeUID THING_TYPE_ON_OFF_LIGHT = new ThingTypeUID(BINDING_ID, "onOff");
public static final ThingTypeUID THING_TYPE_DIMMABLE_LIGHT = new ThingTypeUID(BINDING_ID, "dimmer");
public static final ThingTypeUID THING_TYPE_BLIND = new ThingTypeUID(BINDING_ID, "blind");
public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
public static final ThingTypeUID THING_TYPE_ENERGYMETER = new ThingTypeUID(BINDING_ID, "energyMeter");
// thing type sets
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(BRIDGEI_THING_TYPE, BRIDGEII_THING_TYPE).collect(Collectors.toSet()));
public static final Set<ThingTypeUID> ACTION_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_PUSHBUTTON, THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_BLIND)
.collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.of(THING_TYPE_PUSHBUTTON, THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT,
THING_TYPE_BLIND, THING_TYPE_THERMOSTAT, THING_TYPE_ENERGYMETER).collect(Collectors.toSet()));
// List of all Channel ids
public static final String CHANNEL_BUTTON = "button";
public static final String CHANNEL_SWITCH = "switch";
public static final String CHANNEL_BRIGHTNESS = "brightness";
public static final String CHANNEL_ROLLERSHUTTER = "rollershutter";
public static final String CHANNEL_MEASURED = "measured";
public static final String CHANNEL_SETPOINT = "setpoint";
public static final String CHANNEL_OVERRULETIME = "overruletime";
public static final String CHANNEL_MODE = "mode";
public static final String CHANNEL_DEMAND = "demand";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_NOTICE = "notice";
// Bridge config properties
public static final String CONFIG_HOST_NAME = "addr";
public static final String CONFIG_PORT = "port";
public static final String CONFIG_REFRESH = "refresh";
public static final String CONFIG_PROFILE = "profile";
public static final String CONFIG_PASSWORD = "password";
// Thing config properties
public static final String CONFIG_ACTION_ID = "actionId";
public static final String CONFIG_STEP_VALUE = "step";
public static final String CONFIG_THERMOSTAT_ID = "thermostatId";
public static final String CONFIG_OVERRULETIME = "overruleTime";
public static final String CONFIG_ENERGYMETER_ID = "energyMeterId";
}

View File

@@ -0,0 +1,116 @@
/**
* 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.nikohomecontrol.internal;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.discovery.NikoHomeControlDiscoveryService;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlActionHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler1;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlEnergyMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
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;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link NikoHomeControlHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Mark Herwege - Initial Contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.nikohomecontrol")
@NonNullByDefault
public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID) || BRIDGE_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
if (BRIDGE_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
NikoHomeControlBridgeHandler handler;
if (BRIDGEII_THING_TYPE.equals(thing.getThingTypeUID())) {
handler = new NikoHomeControlBridgeHandler2((Bridge) thing, networkAddressService);
} else {
handler = new NikoHomeControlBridgeHandler1((Bridge) thing);
}
registerNikoHomeControlDiscoveryService(handler);
return handler;
} else if (THING_TYPE_THERMOSTAT.equals(thing.getThingTypeUID())) {
return new NikoHomeControlThermostatHandler(thing);
} else if (THING_TYPE_ENERGYMETER.equals(thing.getThingTypeUID())) {
return new NikoHomeControlEnergyMeterHandler(thing);
} else if (ACTION_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlActionHandler(thing);
}
return null;
}
private synchronized void registerNikoHomeControlDiscoveryService(NikoHomeControlBridgeHandler bridgeHandler) {
NikoHomeControlDiscoveryService nhcDiscoveryService = new NikoHomeControlDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(), bundleContext
.registerService(DiscoveryService.class.getName(), nhcDiscoveryService, new Hashtable<>()));
nhcDiscoveryService.activate();
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof NikoHomeControlBridgeHandler) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
// remove discovery service, if bridge handler is removed
NikoHomeControlDiscoveryService service = (NikoHomeControlDiscoveryService) bundleContext
.getService(serviceReg.getReference());
serviceReg.unregister();
if (service != null) {
service.deactivate();
}
}
}
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = 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.nikohomecontrol.internal.discovery;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.io.IOException;
import java.net.InetAddress;
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.nikohomecontrol.internal.NikoHomeControlBindingConstants;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlDiscover;
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.config.discovery.DiscoveryService;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NikoHomeControlBridgeDiscoveryService} is used to discover a Niko Home Control IP-interface in the local
* network.
*
* @author Mark Herwege - Initial Contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.nikohomecontrol")
@NonNullByDefault
public class NikoHomeControlBridgeDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeDiscoveryService.class);
private volatile @Nullable ScheduledFuture<?> nhcDiscoveryJob;
private @NonNullByDefault({}) NetworkAddressService networkAddressService;
private static final int TIMEOUT = 5;
private static final int REFRESH_INTERVAL = 60;
public NikoHomeControlBridgeDiscoveryService() {
super(NikoHomeControlBindingConstants.BRIDGE_THING_TYPES_UIDS, TIMEOUT);
logger.debug("Niko Home Control: bridge discovery service started");
}
/**
* Discovers devices connected to a Niko Home Control controller
*/
private void discoverBridge() {
try {
String broadcastAddr = networkAddressService.getConfiguredBroadcastAddress();
if (broadcastAddr == null) {
logger.warn("Niko Home Control: discovery not possible, no broadcast address found");
return;
}
logger.debug("Niko Home Control: discovery broadcast on {}", broadcastAddr);
NikoHomeControlDiscover nhcDiscover = new NikoHomeControlDiscover(broadcastAddr);
if (nhcDiscover.isNhcII()) {
addNhcIIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId());
} else {
addNhcIBridge(nhcDiscover.getAddr(), nhcDiscover.getNhcBridgeId());
}
} catch (IOException e) {
logger.debug("Niko Home Control: no bridge found.");
}
}
private void addNhcIBridge(InetAddress addr, String bridgeId) {
logger.debug("Niko Home Control: NHC I bridge found at {}", addr);
String bridgeName = "Niko Home Control Bridge";
ThingUID uid = new ThingUID(BINDING_ID, "bridge", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build();
thingDiscovered(discoveryResult);
}
private void addNhcIIBridge(InetAddress addr, String bridgeId) {
logger.debug("Niko Home Control: NHC II bridge found at {}", addr);
String bridgeName = "Niko Home Control II Bridge";
ThingUID uid = new ThingUID(BINDING_ID, "bridge2", bridgeId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(uid).withLabel(bridgeName)
.withProperty(CONFIG_HOST_NAME, addr.getHostAddress()).build();
thingDiscovered(discoveryResult);
}
@Override
protected void startScan() {
discoverBridge();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
@Override
protected void startBackgroundDiscovery() {
logger.debug("Niko Home Control: Start background bridge discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job == null || job.isCancelled()) {
nhcDiscoveryJob = scheduler.scheduleWithFixedDelay(this::discoverBridge, 0, REFRESH_INTERVAL,
TimeUnit.SECONDS);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Niko Home Control: Stop bridge background discovery");
ScheduledFuture<?> job = nhcDiscoveryJob;
if (job != null && !job.isCancelled()) {
job.cancel(true);
nhcDiscoveryJob = null;
}
}
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = networkAddressService;
}
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
}

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.nikohomecontrol.internal.discovery;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.util.Date;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlBridgeHandler;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* If a Niko Home Control bridge is added or if the user scans manually for things this
* {@link NikoHomeControlDiscoveryService} is used to return Niko Home Control Actions as things to the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscoveryService.class);
private static final int TIMEOUT = 5;
private ThingUID bridgeUID;
private NikoHomeControlBridgeHandler handler;
public NikoHomeControlDiscoveryService(NikoHomeControlBridgeHandler handler) {
super(SUPPORTED_THING_TYPES_UIDS, TIMEOUT, false);
logger.debug("Niko Home Control: discovery service {}", handler);
bridgeUID = handler.getThing().getUID();
this.handler = handler;
}
public void activate() {
handler.setNhcDiscovery(this);
}
@Override
public void deactivate() {
removeOlderResults(new Date().getTime());
handler.setNhcDiscovery(null);
}
/**
* Discovers devices connected to a Niko Home Control controller
*/
public void discoverDevices() {
NikoHomeControlCommunication nhcComm = handler.getCommunication();
if ((nhcComm == null) || !nhcComm.communicationActive()) {
logger.warn("Niko Home Control: not connected.");
return;
}
logger.debug("Niko Home Control: getting devices on {}", handler.getThing().getUID().getId());
Map<String, NhcAction> actions = nhcComm.getActions();
actions.forEach((actionId, nhcAction) -> {
String thingName = nhcAction.getName();
String thingLocation = nhcAction.getLocation();
switch (nhcAction.getType()) {
case TRIGGER:
addActionDevice(new ThingUID(THING_TYPE_PUSHBUTTON, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case RELAY:
addActionDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case DIMMER:
addActionDevice(new ThingUID(THING_TYPE_DIMMABLE_LIGHT, handler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
break;
case ROLLERSHUTTER:
addActionDevice(new ThingUID(THING_TYPE_BLIND, handler.getThing().getUID(), actionId), actionId,
thingName, thingLocation);
break;
default:
logger.debug("Niko Home Control: unrecognized action type {} for {} {}", nhcAction.getType(),
actionId, thingName);
}
});
Map<String, NhcThermostat> thermostats = nhcComm.getThermostats();
thermostats.forEach((thermostatId, nhcThermostat) -> {
String thingName = nhcThermostat.getName();
String thingLocation = nhcThermostat.getLocation();
addThermostatDevice(new ThingUID(THING_TYPE_THERMOSTAT, handler.getThing().getUID(), thermostatId),
thermostatId, thingName, thingLocation);
});
Map<String, NhcEnergyMeter> energyMeters = nhcComm.getEnergyMeters();
energyMeters.forEach((energyMeterId, nhcEnergyMeter) -> {
String thingName = nhcEnergyMeter.getName();
addEnergyMeterDevice(new ThingUID(THING_TYPE_ENERGYMETER, handler.getThing().getUID(), energyMeterId),
energyMeterId, thingName);
});
}
private void addActionDevice(ThingUID uid, String actionId, String thingName, @Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ACTION_ID, actionId);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build());
}
private void addThermostatDevice(ThingUID uid, String thermostatId, String thingName,
@Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_THERMOSTAT_ID, thermostatId);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build());
}
private void addEnergyMeterDevice(ThingUID uid, String energyMeterId, String thingName) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ENERGYMETER_ID, energyMeterId);
thingDiscovered(discoveryResultBuilder.build());
}
@Override
protected void startScan() {
discoverDevices();
}
@Override
protected synchronized void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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.nikohomecontrol.internal.handler;
import java.util.List;
/**
* {@link NhcJwtToken2} represents the Niko Home Control II hobby API token payload.
*
* @author Mark Herwege - Initial Contribution
*/
class NhcJwtToken2 {
String sub;
String iat;
String exp;
String aud;
String iss;
String jti;
List<String> role;
}

View File

@@ -0,0 +1,22 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlActionConfig} is the general config class for Niko Home Control Actions.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlActionConfig {
public String actionId;
}

View File

@@ -0,0 +1,22 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlActionDimmerConfig} is the config class for Niko Home Control Dimmer Actions.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlActionDimmerConfig extends NikoHomeControlActionConfig {
public int step;
}

View File

@@ -0,0 +1,321 @@
/**
* 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.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcActionEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
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.library.types.StopMoveType;
import org.openhab.core.library.types.UpDownType;
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.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlActionHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlActionHandler extends BaseThingHandler implements NhcActionEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlActionHandler.class);
private volatile @NonNullByDefault({}) NhcAction nhcAction;
private String actionId = "";
private int stepValue;
public NikoHomeControlActionHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to execute action command "
+ actionId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
handleCommandSelection(channelUID, command);
}
});
}
private void handleCommandSelection(ChannelUID channelUID, Command command) {
logger.debug("Niko Home Control: handle command {} for {}", command, channelUID);
if (command == REFRESH) {
actionEvent(nhcAction.getState());
return;
}
switch (channelUID.getId()) {
case CHANNEL_BUTTON:
case CHANNEL_SWITCH:
handleSwitchCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_BRIGHTNESS:
handleBrightnessCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_ROLLERSHUTTER:
handleRollershutterCommand(command);
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: channel unknown " + channelUID.getId());
}
}
private void handleSwitchCommand(Command command) {
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.OFF.equals(s)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(NHCON);
}
}
}
private void handleBrightnessCommand(Command command) {
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.OFF.equals(s)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(NHCON);
}
} else if (command instanceof IncreaseDecreaseType) {
IncreaseDecreaseType s = (IncreaseDecreaseType) command;
int currentValue = nhcAction.getState();
int newValue;
if (IncreaseDecreaseType.INCREASE.equals(s)) {
newValue = currentValue + stepValue;
// round down to step multiple
newValue = newValue - newValue % stepValue;
nhcAction.execute(Integer.toString(newValue > 100 ? 100 : newValue));
} else {
newValue = currentValue - stepValue;
// round up to step multiple
newValue = newValue + newValue % stepValue;
if (newValue <= 0) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(Integer.toString(newValue));
}
}
} else if (command instanceof PercentType) {
PercentType p = (PercentType) command;
if (PercentType.ZERO.equals(p)) {
nhcAction.execute(NHCOFF);
} else {
nhcAction.execute(Integer.toString(p.intValue()));
}
}
}
private void handleRollershutterCommand(Command command) {
if (command instanceof UpDownType) {
UpDownType s = (UpDownType) command;
if (UpDownType.UP.equals(s)) {
nhcAction.execute(NHCUP);
} else {
nhcAction.execute(NHCDOWN);
}
} else if (command instanceof StopMoveType) {
nhcAction.execute(NHCSTOP);
} else if (command instanceof PercentType) {
PercentType p = (PercentType) command;
nhcAction.execute(Integer.toString(100 - p.intValue()));
}
}
@Override
public void initialize() {
NikoHomeControlActionConfig config;
if (thing.getThingTypeUID().equals(THING_TYPE_DIMMABLE_LIGHT)) {
config = getConfig().as(NikoHomeControlActionDimmerConfig.class);
stepValue = ((NikoHomeControlActionDimmerConfig) config).step;
} else {
config = getConfig().as(NikoHomeControlActionConfig.class);
}
actionId = config.actionId;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize action "
+ actionId);
return;
}
nhcAction = nhcComm.getActions().get(actionId);
if (nhcAction == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: actionId does not match an action in the controller " + actionId);
return;
}
nhcAction.setEventHandler(this);
updateProperties();
String actionLocation = nhcAction.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(actionLocation);
}
actionEvent(nhcAction.getState());
logger.debug("Niko Home Control: action initialized {}", actionId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
properties.put("type", String.valueOf(nhcAction.getType()));
if (getThing().getThingTypeUID() == THING_TYPE_BLIND) {
properties.put("timeToOpen", String.valueOf(nhcAction.getOpenTime()));
properties.put("timeToClose", String.valueOf(nhcAction.getCloseTime()));
}
if (nhcAction instanceof NhcAction2) {
NhcAction2 action = (NhcAction2) nhcAction;
properties.put("model", action.getModel());
properties.put("technology", action.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void actionEvent(int actionState) {
ActionType actionType = nhcAction.getType();
switch (actionType) {
case TRIGGER:
updateState(CHANNEL_BUTTON, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
updateStatus(ThingStatus.ONLINE);
case RELAY:
updateState(CHANNEL_SWITCH, (actionState == 0) ? OnOffType.OFF : OnOffType.ON);
updateStatus(ThingStatus.ONLINE);
break;
case DIMMER:
updateState(CHANNEL_BRIGHTNESS, new PercentType(actionState));
updateStatus(ThingStatus.ONLINE);
break;
case ROLLERSHUTTER:
updateState(CHANNEL_ROLLERSHUTTER, new PercentType(actionState));
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: unknown action type " + actionType);
}
}
@Override
public void actionRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: action has been removed from the controller " + actionId);
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for action " + actionId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for action " + actionId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,24 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlBridgeConfig} is the general config class for Niko Home Control Bridges.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlBridgeConfig {
public String addr;
public int port;
public int refresh;
}

View File

@@ -0,0 +1,23 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlBridgeConfig2} is the extended config class for Niko Home Control II Bridges.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlBridgeConfig2 extends NikoHomeControlBridgeConfig {
public String profile;
public String password;
}

View File

@@ -0,0 +1,280 @@
/**
* 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.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Map.Entry;
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.nikohomecontrol.internal.discovery.NikoHomeControlDiscoveryService;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
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;
/**
* {@link NikoHomeControlBridgeHandler} is an abstract class representing a handler to all different interfaces to the
* Niko Home Control System. {@link NikoHomeControlBridgeHandler1} or {@link NikoHomeControlBridgeHandler2} should be
* used for the respective
* version of Niko Home Control.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler implements NhcControllerEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler.class);
protected @NonNullByDefault({}) NikoHomeControlBridgeConfig config;
protected @Nullable NikoHomeControlCommunication nhcComm;
private volatile @Nullable ScheduledFuture<?> refreshTimer;
protected volatile @Nullable NikoHomeControlDiscoveryService nhcDiscovery;
public NikoHomeControlBridgeHandler(Bridge nikoHomeControlBridge) {
super(nikoHomeControlBridge);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// There is nothing to handle in the bridge handler
}
/**
* Create communication object to Niko Home Control IP-interface and start communication.
* Trigger discovery when communication setup is successful.
*/
protected void startCommunication() {
NikoHomeControlCommunication comm = nhcComm;
if (comm == null) {
bridgeOffline();
return;
}
scheduler.submit(() -> {
comm.startCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh;
setupRefreshTimer(refreshInterval);
NikoHomeControlDiscoveryService discovery = nhcDiscovery;
if (discovery != null) {
discovery.discoverDevices();
} else {
logger.debug("Niko Home Control: cannot discover devices, discovery service not started");
}
});
}
/**
* Schedule future communication refresh.
*
* @param interval_config Time before refresh in minutes.
*/
private void setupRefreshTimer(int refreshInterval) {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
refreshTimer = null;
}
if (refreshInterval == 0) {
return;
}
// This timer will restart the bridge connection periodically
logger.debug("Niko Home Control: restart bridge connection every {} min", refreshInterval);
refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
logger.debug("Niko Home Control: restart communication at scheduled time");
NikoHomeControlCommunication comm = nhcComm;
if (comm != null) {
comm.restartCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
}
}, refreshInterval, refreshInterval, TimeUnit.MINUTES);
}
/**
* Take bridge offline when error in communication with Niko Home Control IP-interface.
*/
protected void bridgeOffline() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: error starting bridge connection");
}
/**
* Put bridge online when error in communication resolved.
*/
public void bridgeOnline() {
updateProperties();
updateStatus(ThingStatus.ONLINE);
}
@Override
public void controllerOffline() {
bridgeOffline();
}
@Override
public void controllerOnline() {
bridgeOnline();
int refreshInterval = config.refresh;
if (refreshTimer == null) {
setupRefreshTimer(refreshInterval);
}
}
/**
* Update bridge properties with properties returned from Niko Home Control Controller, so they can be made visible
* in PaperUI.
*/
protected abstract void updateProperties();
@Override
public void dispose() {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
}
refreshTimer = null;
NikoHomeControlCommunication comm = nhcComm;
if (comm != null) {
comm.stopCommunication();
}
nhcComm = null;
}
@Override
public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
NikoHomeControlCommunication comm = nhcComm;
// if the communication had not been started yet, just dispose and initialize again
if (comm == null) {
super.handleConfigurationUpdate(configurationParameters);
return;
}
Configuration configuration = editConfiguration();
for (Entry<String, Object> configurationParmeter : configurationParameters.entrySet()) {
configuration.put(configurationParmeter.getKey(), configurationParmeter.getValue());
}
updateConfiguration(configuration);
setConfig();
scheduler.submit(() -> {
comm.restartCommunication();
if (!comm.communicationActive()) {
bridgeOffline();
return;
}
updateProperties();
updateStatus(ThingStatus.ONLINE);
int refreshInterval = config.refresh;
setupRefreshTimer(refreshInterval);
});
}
/**
* Set discovery service handler to be able to start discovery after bridge initialization.
*
* @param nhcDiscovery
*/
public void setNhcDiscovery(@Nullable NikoHomeControlDiscoveryService nhcDiscovery) {
this.nhcDiscovery = nhcDiscovery;
}
@Override
public void alarmEvent(String alarmText) {
logger.debug("Niko Home Control: triggering alarm channel with {}", alarmText);
triggerChannel(CHANNEL_ALARM, alarmText);
updateStatus(ThingStatus.ONLINE);
}
@Override
public void noticeEvent(String alarmText) {
logger.debug("Niko Home Control: triggering notice channel with {}", alarmText);
triggerChannel(CHANNEL_NOTICE, alarmText);
updateStatus(ThingStatus.ONLINE);
}
@Override
public void updatePropertiesEvent() {
updateProperties();
}
/**
* Get the Niko Home Control communication object.
*
* @return Niko Home Control communication object
*/
public @Nullable NikoHomeControlCommunication getCommunication() {
return nhcComm;
}
@Override
public @Nullable InetAddress getAddr() {
InetAddress addr = null;
try {
addr = InetAddress.getByName(config.addr);
} catch (UnknownHostException e) {
logger.debug("Niko Home Control: Cannot resolve hostname {} to IP adress", config.addr);
}
return addr;
}
@Override
public int getPort() {
return config.port;
}
protected synchronized void setConfig() {
config = getConfig().as(NikoHomeControlBridgeConfig.class);
}
}

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.nikohomecontrol.internal.handler;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NikoHomeControlBridgeHandler1} is the handler for a Niko Home Control I IP-interface and connects it to
* the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBridgeHandler1 extends NikoHomeControlBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler1.class);
public NikoHomeControlBridgeHandler1(Bridge nikoHomeControlBridge) {
super(nikoHomeControlBridge);
}
@Override
public void initialize() {
logger.debug("Niko Home Control: initializing bridge handler");
setConfig();
InetAddress addr = getAddr();
int port = getPort();
logger.debug("Niko Home Control: bridge handler host {}, port {}", addr, port);
if (addr != null) {
nhcComm = new NikoHomeControlCommunication1(this, scheduler);
startCommunication();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: cannot resolve bridge IP with hostname " + config.addr);
}
}
@Override
protected void updateProperties() {
Map<String, String> properties = new HashMap<>();
NikoHomeControlCommunication1 comm = (NikoHomeControlCommunication1) nhcComm;
if (comm != null) {
properties.put("softwareVersion", comm.getSystemInfo().getSwVersion());
properties.put("apiVersion", comm.getSystemInfo().getApi());
properties.put("language", comm.getSystemInfo().getLanguage());
properties.put("currency", comm.getSystemInfo().getCurrency());
properties.put("units", comm.getSystemInfo().getUnits());
properties.put("tzOffset", comm.getSystemInfo().getTz());
properties.put("dstOffset", comm.getSystemInfo().getDst());
properties.put("configDate", comm.getSystemInfo().getLastConfig());
properties.put("energyEraseDate", comm.getSystemInfo().getLastEnergyErase());
properties.put("connectionStartDate", comm.getSystemInfo().getTime());
thing.setProperties(properties);
}
}
}

View File

@@ -0,0 +1,236 @@
/**
* 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.nikohomecontrol.internal.handler;
import java.security.cert.CertificateException;
import java.text.DateFormat;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
/**
* {@link NikoHomeControlBridgeHandler2} is the handler for a Niko Home Control II Connected Controller and connects it
* to the framework.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlBridgeHandler2 extends NikoHomeControlBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBridgeHandler2.class);
private final Gson gson = new GsonBuilder().create();
NetworkAddressService networkAddressService;
public NikoHomeControlBridgeHandler2(Bridge nikoHomeControlBridge, NetworkAddressService networkAddressService) {
super(nikoHomeControlBridge);
this.networkAddressService = networkAddressService;
}
@Override
public void initialize() {
logger.debug("Niko Home Control: initializing NHC II bridge handler");
setConfig();
Date expiryDate = getTokenExpiryDate();
if (expiryDate == null) {
if (getToken().isEmpty()) {
// We allow a not well formed token (no expiry date) to pass through.
// This allows the user to use this as a password on a profile, with the profile UUID defined in an
// advanced configuration, skipping token validation.
// This behavior would allow the same logic to be used (with profile UUID) as before token validation
// was introduced.
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Niko Home Control: token is empty");
return;
}
} else {
Date now = new Date();
if (expiryDate.before(now)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR,
"Niko Home Control: hobby api token has expired");
return;
}
}
String addr = networkAddressService.getPrimaryIpv4HostAddress();
addr = (addr == null) ? "unknown" : addr.replace(".", "_");
String clientId = addr + "-" + thing.getUID().toString().replace(":", "_");
try {
nhcComm = new NikoHomeControlCommunication2(this, clientId, scheduler);
startCommunication();
} catch (CertificateException e) {
// this should not happen unless there is a programming error
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
"Niko Home Control: not able to set SSL context");
return;
}
}
@Override
protected void updateProperties() {
Map<String, String> properties = new HashMap<>();
NikoHomeControlCommunication2 comm = (NikoHomeControlCommunication2) nhcComm;
if (comm != null) {
Date expiry = getTokenExpiryDate();
if (expiry != null) {
properties.put("tokenExpiryDate", DateFormat.getDateInstance().format(expiry));
}
String nhcVersion = comm.getSystemInfo().getNhcVersion();
if (!nhcVersion.isEmpty()) {
properties.put("nhcVersion", nhcVersion);
}
String cocoImage = comm.getSystemInfo().getCocoImage();
if (!cocoImage.isEmpty()) {
properties.put("cocoImage", cocoImage);
}
String language = comm.getSystemInfo().getLanguage();
if (!language.isEmpty()) {
properties.put("language", language);
}
String currency = comm.getSystemInfo().getCurrency();
if (!currency.isEmpty()) {
properties.put("currency", currency);
}
String units = comm.getSystemInfo().getUnits();
if (!units.isEmpty()) {
properties.put("units", units);
}
String lastConfig = comm.getSystemInfo().getLastConfig();
if (!lastConfig.isEmpty()) {
properties.put("lastConfig", lastConfig);
}
String electricityTariff = comm.getSystemInfo().getElectricityTariff();
if (!electricityTariff.isEmpty()) {
properties.put("electricityTariff", electricityTariff);
}
String gasTariff = comm.getSystemInfo().getGasTariff();
if (!gasTariff.isEmpty()) {
properties.put("gasTariff", gasTariff);
}
String waterTariff = comm.getSystemInfo().getWaterTariff();
if (!waterTariff.isEmpty()) {
properties.put("waterTariff", waterTariff);
}
String timezone = comm.getTimeInfo().getTimezone();
if (!timezone.isEmpty()) {
properties.put("timezone", timezone);
}
String isDst = comm.getTimeInfo().getIsDst();
if (!isDst.isEmpty()) {
properties.put("isDST", isDst);
}
String services = comm.getServices();
if (!services.isEmpty()) {
properties.put("services", services);
}
thing.setProperties(properties);
}
}
@Override
public String getProfile() {
return ((NikoHomeControlBridgeConfig2) config).profile;
}
@Override
public String getToken() {
String token = ((NikoHomeControlBridgeConfig2) config).password;
if ((token == null) || token.isEmpty()) {
logger.debug("Niko Home Control: no JWT token set.");
return "";
}
return token;
}
/**
* Extract the expiry date in the user provided token for the hobby API. Log warnings and errors if the token is
* close to expiry or expired.
*
* @return Hobby API token expiry date, null if no valid token.
*/
private @Nullable Date getTokenExpiryDate() {
NhcJwtToken2 jwtToken = null;
String token = getToken();
String[] tokenArray = token.split("\\.");
if (tokenArray.length == 3) {
String tokenPayload = new String(Base64.getDecoder().decode(tokenArray[1]));
try {
jwtToken = gson.fromJson(tokenPayload, NhcJwtToken2.class);
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected token payload {}", tokenPayload);
} catch (NoSuchElementException ignore) {
// Ignore if exp not present in response, this should not happen in token payload response
logger.trace("Niko Home Control: no expiry date found in payload {}", tokenPayload);
}
}
if (jwtToken != null) {
Date expiryDate;
try {
String expiryEpoch = jwtToken.exp;
long epoch = Long.parseLong(expiryEpoch) * 1000; // convert to milliseconds
expiryDate = new Date(epoch);
} catch (NumberFormatException e) {
logger.debug("Niko Home Control: token expiry not valid {}", jwtToken.exp);
return null;
}
Date now = new Date();
if (expiryDate.before(now)) {
logger.warn("Niko Home Control: hobby API token expired, was valid until {}",
DateFormat.getDateInstance().format(expiryDate));
} else {
Calendar c = Calendar.getInstance();
c.setTime(expiryDate);
c.add(Calendar.DATE, -14);
if (c.getTime().before(now)) {
logger.info("Niko Home Control: hobby API token will expire in less than 14 days, valid until {}",
DateFormat.getDateInstance().format(expiryDate));
}
}
return expiryDate;
}
return null;
}
@Override
protected synchronized void setConfig() {
config = getConfig().as(NikoHomeControlBridgeConfig2.class);
}
}

View File

@@ -0,0 +1,22 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlEnergyMeterConfig} is the config class for Niko Home Control Thermostats.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlEnergyMeterConfig {
public String energyMeterId;
}

View File

@@ -0,0 +1,241 @@
/**
* 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.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.CHANNEL_POWER;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeterEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcEnergyMeter2;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SmartHomeUnits;
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.types.Command;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlEnergyMeterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlEnergyMeterHandler extends BaseThingHandler implements NhcEnergyMeterEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlEnergyMeterHandler.class);
private volatile @NonNullByDefault({}) NhcEnergyMeter nhcEnergyMeter;
private String energyMeterId = "";
public NikoHomeControlEnergyMeterHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command == REFRESH) {
energyMeterEvent(nhcEnergyMeter.getPower());
}
}
@Override
public void initialize() {
NikoHomeControlEnergyMeterConfig config = getConfig().as(NikoHomeControlEnergyMeterConfig.class);
energyMeterId = config.energyMeterId;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize energy meter "
+ energyMeterId);
return;
}
nhcEnergyMeter = nhcComm.getEnergyMeters().get(energyMeterId);
if (nhcEnergyMeter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: energyMeterId does not match a energy meter in the controller "
+ energyMeterId);
return;
}
nhcEnergyMeter.setEventHandler(this);
updateProperties();
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
// linked to the channel
if (isLinked(CHANNEL_POWER)) {
nhcComm.startEnergyMeter(energyMeterId);
}
logger.debug("Niko Home Control: energy meter intialized {}", energyMeterId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm != null) {
nhcComm.stopEnergyMeter(energyMeterId);
}
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
if (nhcEnergyMeter instanceof NhcEnergyMeter2) {
NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) nhcEnergyMeter;
properties.put("model", energyMeter.getModel());
properties.put("technology", energyMeter.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void energyMeterEvent(@Nullable Integer power) {
if (power == null) {
updateState(CHANNEL_POWER, UnDefType.UNDEF);
} else {
updateState(CHANNEL_POWER, new QuantityType<>(power, SmartHomeUnits.WATT));
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void energyMeterRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: energy meter has been removed from the controller " + energyMeterId);
}
@Override
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item linked to
// the channel
public void channelLinked(ChannelUID channelUID) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to start energy meter "
+ energyMeterId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.startEnergyMeter(energyMeterId);
updateStatus(ThingStatus.ONLINE);
}
});
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to stop energy meter "
+ energyMeterId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.stopEnergyMeter(energyMeterId);
// as this is momentary power production/consumption, we set it UNDEF as we do not get readings anymore
updateState(CHANNEL_POWER, UnDefType.UNDEF);
updateStatus(ThingStatus.ONLINE);
}
});
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for energy meter " + energyMeterId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for energy meter " + energyMeterId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,23 @@
/**
* 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.nikohomecontrol.internal.handler;
/**
* {@link NikoHomeControlThermostatConfig} is the config class for Niko Home Control Thermostats.
*
* @author Mark Herwege - Initial Contribution
*/
public class NikoHomeControlThermostatConfig {
public String thermostatId;
public int overruleTime;
}

View File

@@ -0,0 +1,310 @@
/**
* 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.nikohomecontrol.internal.handler;
import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindingConstants.*;
import static org.openhab.core.library.unit.SIUnits.CELSIUS;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostatEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
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.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlThermostatHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
private volatile @NonNullByDefault({}) NhcThermostat nhcThermostat;
private String thermostatId = "";
private int overruleTime;
private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
// minute
public NikoHomeControlThermostatHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: bridge communication not initialized when trying to execute thermostat command "
+ thermostatId);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
handleCommandSelection(channelUID, command);
}
});
}
private void handleCommandSelection(ChannelUID channelUID, Command command) {
logger.debug("Niko Home Control: handle command {} for {}", command, channelUID);
if (REFRESH.equals(command)) {
thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
nhcThermostat.getOverrule(), nhcThermostat.getDemand());
return;
}
switch (channelUID.getId()) {
case CHANNEL_MEASURED:
case CHANNEL_DEMAND:
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_MODE:
if (command instanceof DecimalType) {
nhcThermostat.executeMode(((DecimalType) command).intValue());
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_SETPOINT:
QuantityType<Temperature> setpoint = null;
if (command instanceof QuantityType) {
setpoint = ((QuantityType<Temperature>) command).toUnit(CELSIUS);
// Always set the new setpoint temperature as an overrule
// If no overrule time is given yet, set the overrule time to the configuration parameter
int time = nhcThermostat.getOverruletime();
if (time <= 0) {
time = overruleTime;
}
if (setpoint != null) {
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
}
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_OVERRULETIME:
if (command instanceof DecimalType) {
int overruletime = ((DecimalType) command).intValue();
int overrule = nhcThermostat.getOverrule();
if (overruletime <= 0) {
overruletime = 0;
overrule = 0;
}
nhcThermostat.executeOverrule(overrule, overruletime);
}
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: channel unknown " + channelUID.getId());
}
}
@Override
public void initialize() {
NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
thermostatId = config.thermostatId;
overruleTime = config.overruleTime;
NikoHomeControlCommunication nhcComm = getCommunication();
if (nhcComm == null) {
return;
}
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: no connection with Niko Home Control, could not initialize thermostat "
+ thermostatId);
return;
}
nhcThermostat = nhcComm.getThermostats().get(thermostatId);
if (nhcThermostat == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: thermostatId does not match a thermostat in the controller "
+ thermostatId);
return;
}
nhcThermostat.setEventHandler(this);
updateProperties();
String thermostatLocation = nhcThermostat.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(thermostatLocation);
}
thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
nhcThermostat.getOverrule(), nhcThermostat.getDemand());
logger.debug("Niko Home Control: thermostat intialized {}", thermostatId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
});
}
private void updateProperties() {
Map<String, String> properties = new HashMap<>();
if (nhcThermostat instanceof NhcThermostat2) {
NhcThermostat2 thermostat = (NhcThermostat2) nhcThermostat;
properties.put("model", thermostat.getModel());
properties.put("technology", thermostat.getTechnology());
}
thing.setProperties(properties);
}
@Override
public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
updateState(CHANNEL_MEASURED, new QuantityType<>(nhcThermostat.getMeasured() / 10.0, CELSIUS));
int overruletime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(overruletime));
// refresh the remaining time every minute
scheduleRefreshOverruletime(nhcThermostat);
// If there is an overrule temperature set, use this in the setpoint channel, otherwise use the original
// setpoint temperature
if (overruletime == 0) {
updateState(CHANNEL_SETPOINT, new QuantityType<>(setpoint / 10.0, CELSIUS));
} else {
updateState(CHANNEL_SETPOINT, new QuantityType<>(overrule / 10.0, CELSIUS));
}
updateState(CHANNEL_MODE, new DecimalType(mode));
updateState(CHANNEL_DEMAND, new DecimalType(demand));
updateStatus(ThingStatus.ONLINE);
}
/**
* Method to update state of overruletime channel every minute with remaining time.
*
* @param NhcThermostat object
*
*/
private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
cancelRefreshTimer();
if (nhcThermostat.getRemainingOverruletime() <= 0) {
return;
}
refreshTimer = scheduler.scheduleWithFixedDelay(() -> {
int remainingTime = nhcThermostat.getRemainingOverruletime();
updateState(CHANNEL_OVERRULETIME, new DecimalType(remainingTime));
if (remainingTime <= 0) {
cancelRefreshTimer();
}
}, 1, 1, TimeUnit.MINUTES);
}
private void cancelRefreshTimer() {
ScheduledFuture<?> timer = refreshTimer;
if (timer != null) {
timer.cancel(true);
}
refreshTimer = null;
}
@Override
public void thermostatRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Niko Home Control: thermostat has been removed from the controller " + thermostatId);
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.restartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Niko Home Control: communication socket error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
}
}
private @Nullable NikoHomeControlCommunication getCommunication() {
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for thermostat " + thermostatId);
return null;
}
NikoHomeControlCommunication nhcComm = nhcBridgeHandler.getCommunication();
return nhcComm;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
if (nhcBridge == null) {
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"Niko Home Control: no bridge initialized for thermostat " + thermostatId);
return null;
}
NikoHomeControlBridgeHandler nhcBridgeHandler = (NikoHomeControlBridgeHandler) nhcBridge.getHandler();
return nhcBridgeHandler;
}
}

View File

@@ -0,0 +1,196 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcAction1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction} class represents the action Niko Home Control communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates. Specific implementation are {@link NhcAction1} and {@link NhcAction2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
protected ActionType type;
protected @Nullable String location;
protected volatile int state;
protected volatile int closeTime = 0;
protected volatile int openTime = 0;
@Nullable
private NhcActionEvent eventHandler;
protected NhcAction(String id, String name, ActionType type, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.type = type;
this.location = location;
this.nhcComm = nhcComm;
}
/**
* This method should be called when an object implementing the {@NhcActionEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcActionEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the action.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of action.
*
* @return action name
*/
public String getName() {
return name;
}
/**
* Get type of action identified.
* <p>
* ActionType can be RELAY (for simple light or socket switch), DIMMER, ROLLERSHUTTER, TRIGGER or GENERIC.
*
* @return {@link ActionType}
*/
public ActionType getType() {
return type;
}
/**
* Get location name of action.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Get state of action.
* <p>
* State is a value between 0 and 100 for a dimmer or rollershutter.
* Rollershutter state is 0 for fully closed and 100 for fully open.
* State is 0 or 100 for a switch.
*
* @return action state
*/
public int getState() {
return state;
}
/**
* Set openTime and closeTime for rollershutter action.
* <p>
* Time is in seconds to fully open or close a rollershutter.
*
* @param openTime
* @param closeTime
*/
public void setShutterTimes(int openTime, int closeTime) {
this.openTime = openTime;
this.closeTime = closeTime;
}
/**
* Get openTime of action.
* <p>
* openTime is the time in seconds to fully open a rollershutter.
*
* @return action openTime
*/
public int getOpenTime() {
return openTime;
}
/**
* Get closeTime of action.
* <p>
* closeTime is the time in seconds to fully close a rollershutter.
*
* @return action closeTime
*/
public int getCloseTime() {
return closeTime;
}
protected void updateState() {
updateState(state);
}
protected void updateState(int state) {
NhcActionEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
logger.debug("Niko Home Control: update channel state for {} with {}", id, state);
eventHandler.actionEvent(state);
}
}
/**
* Method called when action is removed from the Niko Home Control Controller.
*/
public void actionRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcActionEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.actionRemoved();
}
}
/**
* Sets state of action. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
*
* @param state - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
public abstract void setState(int state);
/**
* Sends action to Niko Home Control. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
*
* @param command - The allowed values depend on the action type.
* switch action: On or Off
* dimmer action: between 0 and 100, On or Off
* rollershutter action: between 0 and 100, Up, Down or Stop
*/
public abstract void execute(String command);
}

View File

@@ -0,0 +1,40 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcActionEvent} interface is used to pass action events received from the Niko Home Control controller to
* the consuming client. It is designed to pass events to openHAB handlers that implement this interface. Because of
* the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent
* of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcActionEvent {
/**
* This method is called when an action event is received from the Niko Home Control controller.
*
* @param state
*/
public void actionEvent(int state);
/**
* Called to indicate the action has been removed from the Niko Home Control controller.
*
*/
public void actionRemoved();
}

View File

@@ -0,0 +1,97 @@
/**
* 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.nikohomecontrol.internal.protocol;
import java.net.InetAddress;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link NhcControllerEvent} interface is used to get configuration information and to pass alarm or notice events
* received from the Niko Home Control controller to the consuming client. It is designed to pass events to openHAB
* handlers that implement this interface. Because of the design, the
* org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcControllerEvent {
/**
* Get the IP-address of the Niko Home Control IP-interface.
*
* @return the addr
*/
public default @Nullable InetAddress getAddr() {
return null;
}
/**
* Get the listening port of the Niko Home Control IP-interface.
*
* @return the port
*/
public default int getPort() {
return 0;
}
/**
* Get the profile for the hobby API in the Niko Home Control II system.
*
* @return the profile
*/
public default String getProfile() {
return "";
}
/**
* Get the JWT Token of the Niko Home Control II system.
*
* @return the token
*/
public default String getToken() {
return "";
}
/**
* Called to indicate the connection with the Niko Home Control Controller is offline.
*
*/
public void controllerOffline();
/**
* Called to indicate the connection with the Niko Home Control Controller is online.
*
*/
public void controllerOnline();
/**
* This method is called when an alarm event is received from the Niko Home Control controller.
*
* @param alarmText
*/
public void alarmEvent(String alarmText);
/**
* This method is called when a notice event is received from the Niko Home Control controller.
*
* @param alarmText
*/
public void noticeEvent(String noticeText);
/**
* This method is called when properties are updated from from the Niko Home Control controller.
*/
public void updatePropertiesEvent();
}

View File

@@ -0,0 +1,124 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcEnergyMeter2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It
* contains all
* fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage information.
* A specific
* implementation is {@link NhcEnergyMeter2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcEnergyMeter {
private final Logger logger = LoggerFactory.getLogger(NhcEnergyMeter.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
// This can be null as long as we do not receive power readings
protected volatile @Nullable Integer power = null;
private @Nullable NhcEnergyMeterEvent eventHandler;
protected NhcEnergyMeter(String id, String name, NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.nhcComm = nhcComm;
}
/**
* Update all values of the energyMeters meter without touching the energyMeters meter definition (id, name) and
* without changing the ThingHandler callback.
*
* @param power current power consumption/production in W (positive for consumption)
*/
public void updateState(int power) {
NhcEnergyMeterEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update channel for {}", id);
handler.energyMeterEvent(power);
}
}
/**
* Method called when energyMeters meter is removed from the Niko Home Control Controller.
*/
public void energyMeterRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcEnergyMeterEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.energyMeterRemoved();
}
}
/**
* This method should be called when an object implementing the {@NhcEnergyMeterEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcEnergyMeterEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the energyMeters meter.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of the energyMeters meter.
*
* @return energyMeters meter name
*/
public String getName() {
return name;
}
/**
* @return the power in W (positive for consumption, negative for production), return null if no reading received
* yet
*/
public @Nullable Integer getPower() {
return power;
}
/**
* @param power the power to set in W (positive for consumption, negative for production), null if an empty reading
* was received
*/
public void setPower(@Nullable Integer power) {
this.power = power;
NhcEnergyMeterEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update power channel for {} with {}", id, power);
handler.energyMeterEvent(power);
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link NhcEnergyMeterEvent} interface is used to pass energyMeters meter events received from the Niko Home
* Control
* controller to the consuming client. It is designed to pass events to openHAB handlers that implement this interface.
* Because of the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used
* independent of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcEnergyMeterEvent {
/**
* This method is called when an energyMeters meter event is received from the Niko Home Control controller.
*
* @param power current power consumption/production in W (positive for consumption), null for an empty reading
*/
public void energyMeterEvent(@Nullable Integer power);
/**
* Called to indicate the energyMeters meter has been removed from the Niko Home Control controller.
*
*/
public void energyMeterRemoved();
}

View File

@@ -0,0 +1,319 @@
/**
* 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.nikohomecontrol.internal.protocol;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcThermostat1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat} class represents the thermostat Niko Home Control communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates. Specific implementation are {@link NhcThermostat1} and {@link NhcThermostat2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat.class);
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected String name;
protected @Nullable String location;
protected volatile int measured;
protected volatile int setpoint;
protected volatile int mode;
protected volatile int overrule;
protected volatile int overruletime;
protected volatile int ecosave;
protected volatile int demand;
private @Nullable LocalDateTime overruleStart;
private @Nullable NhcThermostatEvent eventHandler;
protected NhcThermostat(String id, String name, @Nullable String location, NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.location = location;
this.nhcComm = nhcComm;
}
/**
* Update all values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param measured current temperature in 0.1°C multiples
* @param setpoint the setpoint temperature in 0.1°C multiples
* @param mode thermostat mode 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog1, 6 = prog2, 7 =
* prog3
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
* @param ecosave
* @param demand 0 if no demand, > 0 if heating, < 0 if cooling
*/
public void updateState(int measured, int setpoint, int mode, int overrule, int overruletime, int ecosave,
int demand) {
setMeasured(measured);
setSetpoint(setpoint);
setMode(mode);
setOverrule(overrule);
setOverruletime(overruletime);
setEcosave(ecosave);
setDemand(demand);
updateChannels();
}
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int overrule, int overruletime) {
setOverrule(overrule);
setOverruletime(overruletime);
updateChannels();
}
/**
* Update overrule values of the thermostat without touching the thermostat definition (id, name and location) and
* without changing the ThingHandler callback.
*
* @param overrule the overrule temperature in 0.1°C multiples
* @param overruletime in minutes
*/
public void updateState(int mode) {
setMode(mode);
updateChannels();
}
/**
* Method called when thermostat is removed from the Niko Home Control Controller.
*/
public void thermostatRemoved() {
logger.warn("Niko Home Control: action removed {}, {}", id, name);
NhcThermostatEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.thermostatRemoved();
}
}
private void updateChannels() {
NhcThermostatEvent handler = eventHandler;
if (handler != null) {
logger.debug("Niko Home Control: update channels for {}", id);
handler.thermostatEvent(measured, setpoint, mode, overrule, demand);
}
}
/**
* This method should be called when an object implementing the {@NhcThermostatEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the action receives an update
* from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcThermostatEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* Get the id of the thermostat.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of thermostat.
*
* @return thermostat name
*/
public String getName() {
return name;
}
/**
* Get location name of action.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Get measured temperature.
*
* @return measured temperature in 0.1°C multiples
*/
public int getMeasured() {
return measured;
}
private void setMeasured(int measured) {
this.measured = measured;
}
/**
* @return the setpoint temperature in 0.1°C multiples
*/
public int getSetpoint() {
return setpoint;
}
private void setSetpoint(int setpoint) {
this.setpoint = setpoint;
}
/**
* Get the thermostat mode.
*
* @return the mode:
* 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog 1, 6 = prog 2, 7 = prog 3
*/
public int getMode() {
return mode;
}
private void setMode(int mode) {
this.mode = mode;
}
/**
* Get the overrule temperature.
*
* @return the overrule temperature in 0.1°C multiples
*/
public int getOverrule() {
if (overrule > 0) {
return overrule;
} else {
return setpoint;
}
}
private void setOverrule(int overrule) {
this.overrule = overrule;
}
/**
* Get the duration for an overrule temperature
*
* @return the overruletime in minutes
*/
public int getOverruletime() {
return overruletime;
}
/**
* Set the duration for an overrule temperature
*
* @param overruletime the overruletime in minutes
*/
private void setOverruletime(int overruletime) {
if (overruletime <= 0) {
stopOverrule();
} else if (overruletime != this.overruletime) {
startOverrule();
}
this.overruletime = overruletime;
}
/**
* @return the ecosave mode
*/
public int getEcosave() {
return ecosave;
}
/**
* @param ecosave the ecosave mode to set
*/
private void setEcosave(int ecosave) {
this.ecosave = ecosave;
}
/**
* @return the heating/cooling demand: 0 if no demand, >0 if heating, <0 if cooling
*/
public int getDemand() {
return demand;
}
/**
* @param demand set the heating/cooling demand
*/
private void setDemand(int demand) {
this.demand = demand;
}
/**
* Sends thermostat mode to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
*
* @param mode
*/
public abstract void executeMode(int mode);
/**
* Sends thermostat setpoint to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
*
* @param overrule temperature to overrule the setpoint in 0.1°C multiples
* @param time time duration in min for overrule
*/
public abstract void executeOverrule(int overrule, int overruletime);
/**
* @return remaining overrule time in minutes
*/
public int getRemainingOverruletime() {
if (overruleStart == null) {
return 0;
} else {
// overruletime time max 23h59min, therefore can safely cast to int
return overruletime - (int) ChronoUnit.MINUTES.between(overruleStart, LocalDateTime.now());
}
}
/**
* Start a new overrule, this method is used to be able to calculate the remaining overrule time
*/
private void startOverrule() {
overruleStart = LocalDateTime.now();
}
/**
* Reset overrule start
*/
private void stopOverrule() {
overruleStart = null;
}
}

View File

@@ -0,0 +1,45 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcThermostatEvent} interface is used to pass thermostat events received from the Niko Home Control
* controller to
* the consuming client. It is designed to pass events to openHAB handlers that implement this interface. Because of
* the design, the org.openhab.binding.nikohomecontrol.internal.protocol package can be extracted and used independent
* of openHAB.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcThermostatEvent {
/**
* This method is called when thermostat event is received from the Niko Home Control controller.
*
* @param measured current temperature in 0.1°C multiples
* @param setpoint the setpoint temperature in 0.1°C multiples
* @param mode thermostat mode 0 = day, 1 = night, 2 = eco, 3 = off, 4 = cool, 5 = prog1, 6 = prog2, 7 = prog3
* @param overrule the overrule temperature in 0.1°C multiples
* @param demand 0 if no demand, > 0 if heating, < 0 if cooling
*/
public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand);
/**
* Called to indicate the thermostat has been removed from the Niko Home Control controller.
*
*/
public void thermostatRemoved();
}

View File

@@ -0,0 +1,145 @@
/**
* 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.nikohomecontrol.internal.protocol;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlCommunication} class is an abstract class representing the communication objects with the
* Niko Home Control System. {@link NikoHomeControlCommunication1} or {@link NikoHomeControlCommunication2} should be
* used for the respective version of Niko Home Control.
* <ul>
* <li>Start and stop communication with the Niko Home Control System.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen to events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NikoHomeControlCommunication {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication.class);
protected final Map<String, NhcAction> actions = new ConcurrentHashMap<>();
protected final Map<String, NhcThermostat> thermostats = new ConcurrentHashMap<>();
protected final Map<String, NhcEnergyMeter> energyMeters = new ConcurrentHashMap<>();
protected final NhcControllerEvent handler;
protected NikoHomeControlCommunication(NhcControllerEvent handler) {
this.handler = handler;
}
/**
* Start Communication with Niko Home Control system.
*/
public abstract void startCommunication();
/**
* Stop Communication with Niko Home Control system.
*/
public abstract void stopCommunication();
/**
* Close and restart communication with Niko Home Control system.
*/
public synchronized void restartCommunication() {
stopCommunication();
logger.debug("Niko Home Control: restart communication from thread {}", Thread.currentThread().getId());
startCommunication();
}
/**
* Method to check if communication with Niko Home Control is active.
*
* @return True if active
*/
public abstract boolean communicationActive();
/**
* Return all actions in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcAction}></code>
*/
public Map<String, NhcAction> getActions() {
return actions;
}
/**
* Return all thermostats in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcThermostat}></code>
*/
public Map<String, NhcThermostat> getThermostats() {
return thermostats;
}
/**
* Return all energyMeters meters in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcEnergyMeter}></code>
*/
public Map<String, NhcEnergyMeter> getEnergyMeters() {
return energyMeters;
}
/**
* Execute an action command by sending it to Niko Home Control.
*
* @param actionId
* @param value
*/
public abstract void executeAction(String actionId, String value);
/**
* Execute a thermostat command by sending it to Niko Home Control.
*
* @param thermostatId
* @param mode
*/
public abstract void executeThermostat(String thermostatId, String mode);
/**
* Execute a thermostat command by sending it to Niko Home Control.
*
* @param thermostatId
* @param overruleTemp
* @param overruleTime
*/
public abstract void executeThermostat(String thermostatId, int overruleTemp, int overruleTime);
/**
* Start retrieving energy meter data from Niko Home Control.
*
*/
public void startEnergyMeter(String energyMeterId) {
};
/**
* Stop retrieving energy meter data from Niko Home Control.
*
*/
public void stopEnergyMeter(String energyMeterId) {
};
}

View File

@@ -0,0 +1,47 @@
/**
* 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.nikohomecontrol.internal.protocol;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NikoHomeControlConstants} class defines common constants used in the Niko Home Control communication.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlConstants {
// Action types abstracted from NhcI and NhcII action types
public static enum ActionType {
TRIGGER,
RELAY,
DIMMER,
ROLLERSHUTTER,
GENERIC
}
// switch and dimmer constants in the Nhc layer
public static final String NHCON = "On";
public static final String NHCOFF = "Off";
public static final String NHCTRIGGERED = "Triggered";
// rollershutter constants in the Nhc layer
public static final String NHCDOWN = "Down";
public static final String NHCUP = "Up";
public static final String NHCSTOP = "Stop";
// NhcII thermostat modes
public static final String[] THERMOSTATMODES = { "Day", "Night", "Eco", "Off", "Cool", "Prog1", "Prog2", "Prog3" };
}

View File

@@ -0,0 +1,154 @@
/**
* 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.nikohomecontrol.internal.protocol;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class {@link NikoHomeControlDiscover} is used to get the Niko Home Control IP-interface IP address for bridge
* discovery.
* <p>
* The constructor broadcasts a UDP packet with content 0x44 on port 10000.
* The Niko Home Control IP-interface responds to this UDP packet.
* The IP-address from the Niko Home Control IP-interface is then extracted from the response packet.
* The data content of the response packet is used as a unique identifier for the bridge.
*
* @author Mark Herwege - Initial Contribution
*/
/**
* @author Mark Herwege - Initial Contribution
*
*/
@NonNullByDefault
public final class NikoHomeControlDiscover {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class);
private InetAddress addr;
private String nhcBridgeId = "";
private boolean isNhcII;
/**
* Discover a Niko Home Control IP interface by broadcasting UDP packet 0x44 to port 10000. The IP interface will
* reply. The address of the IP interface is than derived from that response.
*
* @param broadcast Broadcast address of the network
* @throws IOException
*/
public NikoHomeControlDiscover(String broadcast) throws IOException {
final byte[] discoverBuffer = { (byte) 0x44 };
final InetAddress broadcastAddr = InetAddress.getByName(broadcast);
final int broadcastPort = 10000;
DatagramPacket discoveryPacket = new DatagramPacket(discoverBuffer, discoverBuffer.length, broadcastAddr,
broadcastPort);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try (DatagramSocket datagramSocket = new DatagramSocket(null)) {
datagramSocket.setBroadcast(true);
datagramSocket.setSoTimeout(500);
datagramSocket.send(discoveryPacket);
while (true) {
datagramSocket.receive(packet);
logger.trace("Niko Home Control: bridge discovery response {}",
HexUtils.bytesToHex(Arrays.copyOf(packet.getData(), packet.getLength())));
if (isNhc(packet)) {
break;
}
}
addr = packet.getAddress();
setNhcBridgeId(packet);
setIsNhcII(packet);
logger.debug("Niko Home Control: IP address is {}, unique ID is {}", addr, nhcBridgeId);
}
}
/**
* @return the addr
*/
public InetAddress getAddr() {
return addr;
}
/**
* @return the nhcBridgeId
*/
public String getNhcBridgeId() {
return nhcBridgeId;
}
/**
* Check if the UDP packet comes from a Niko Home Control controller. The response should start with 0x44.
*
* @param packet
* @return true if packet is from a Niko Home Control controller
*/
private boolean isNhc(DatagramPacket packet) {
byte[] packetData = packet.getData();
if ((packet.getLength() > 2) && (packetData[0] == 0x44)) {
return true;
}
return false;
}
/**
* Retrieves a unique ID from the returned datagram packet received after sending the UDP discovery message.
*
* @param packet
*/
private void setNhcBridgeId(DatagramPacket packet) {
byte[] packetData = packet.getData();
int packetLength = packet.getLength();
packetLength = packetLength > 6 ? 6 : packetLength;
StringBuilder sb = new StringBuilder(packetLength);
for (int i = 0; i < packetLength; i++) {
sb.append(String.format("%02x", packetData[i]));
}
nhcBridgeId = sb.toString();
}
/**
* Checks if this is a NHC II Connected Controller
*
* @param packet
*/
private void setIsNhcII(DatagramPacket packet) {
byte[] packetData = packet.getData();
int packetLength = packet.getLength();
// The 16th byte in the packet is 2 for a NHC II Connected Controller
if ((packetLength >= 16) && (packetData[15] >= 2)) {
isNhcII = true;
} else {
isNhcII = false;
}
}
/**
* Test if the installation is a Niko Home Control II installation
*
* @return true if this is a Niko Home Control II installation
*/
public boolean isNhcII() {
return isNhcII;
}
}

View File

@@ -0,0 +1,304 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import java.util.concurrent.ScheduledExecutorService;
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.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction1} class represents the action Niko Home Control I communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcAction1 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction1.class);
@FunctionalInterface
private interface Action {
void execute();
}
private ScheduledExecutorService scheduler;
private volatile @Nullable Action rollershutterTask;
private volatile @Nullable ScheduledFuture<?> rollershutterStopTask;
private volatile @Nullable ScheduledFuture<?> rollershutterMovingFlagTask;
private volatile boolean filterEvent = false; // flag to filter first event from rollershutter on percent move to
// avoid wrong position update
private volatile boolean rollershutterMoving = false; // flag to indicate if rollershutter is currently moving
private volatile boolean waitForEvent = false; // flag to wait for position update rollershutter before doing next
// move
NhcAction1(String id, String name, ActionType type, @Nullable String location, NikoHomeControlCommunication nhcComm,
ScheduledExecutorService scheduler) {
super(id, name, type, location, nhcComm);
this.scheduler = scheduler;
}
/**
* Sets state of action. This is the version for Niko Home Control I.
*
* @param newState - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
@Override
public void setState(int newState) {
if (getType() == ActionType.ROLLERSHUTTER) {
if (filterEvent) {
filterEvent = false;
logger.debug("Niko Home Control: filtered event {} for {}", newState, id);
return;
}
cancelRollershutterStop();
if (((newState == 0) || (newState == 100)) && (newState != state)) {
long duration = rollershutterMoveTime(state, newState);
setRollershutterMovingTrue(duration);
} else {
setRollershutterMovingFalse();
}
}
if (waitForEvent) {
logger.debug("Niko Home Control: received requested rollershutter {} position event {}", id, newState);
executeRollershutterTask();
} else {
state = newState;
updateState();
}
}
/**
* Sends action to Niko Home Control. This version is used for Niko Home Control I.
*
* @param command - The allowed values depend on the action type.
*/
@Override
public void execute(String command) {
logger.debug("Niko Home Control: execute action {} of type {} for {}", command, type, id);
String value = "";
switch (getType()) {
case GENERIC:
case TRIGGER:
case RELAY:
if (command.equals(NHCON)) {
value = "100";
} else {
value = "0";
}
nhcComm.executeAction(id, value);
break;
case DIMMER:
if (command.equals(NHCON)) {
value = "254";
} else if (command.equals(NHCOFF)) {
value = "255";
} else {
value = command;
}
nhcComm.executeAction(id, value);
break;
case ROLLERSHUTTER:
executeRollershutter(command);
break;
}
}
private void executeRollershutter(String command) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} command {}", id, command);
logger.trace("handleRollerShutterCommand: rollershutter {}, current position {}", id, state);
}
// first stop all current movement of rollershutter and wait until exact position is known
if (rollershutterMoving) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} moving, therefore stop", id);
}
rollershutterPositionStop();
}
// task to be executed once exact position received from Niko Home Control
rollershutterTask = () -> {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} task running", id);
}
int currentValue = state;
if (command.equals(NHCDOWN)) {
executeRollershutterDown();
} else if (command.equals(NHCUP)) {
executeRollershutterUp();
} else if (command.equals(NHCSTOP)) {
executeRollershutterStop();
} else {
int newValue = 100 - Integer.parseInt(command);
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} percent command, current {}, new {}", id,
currentValue, newValue);
}
if (currentValue == newValue) {
return;
}
if ((newValue > 0) && (newValue < 100)) {
scheduleRollershutterStop(currentValue, newValue);
}
if (newValue < currentValue) {
executeRollershutterDown();
} else if (newValue > currentValue) {
executeRollershutterUp();
}
}
};
// execute immediately if not waiting for exact position
if (!waitForEvent) {
if (logger.isTraceEnabled()) {
logger.trace("handleRollerShutterCommand: rollershutter {} task executing immediately", id);
}
executeRollershutterTask();
}
}
private void executeRollershutterStop() {
nhcComm.executeAction(id, "253");
}
private void executeRollershutterDown() {
nhcComm.executeAction(id, "254");
}
private void executeRollershutterUp() {
nhcComm.executeAction(id, "255");
}
/**
* Method used to stop rollershutter when moving. This will then result in an exact position to be received, so next
* percentage movements could be done accurately.
*/
private void rollershutterPositionStop() {
if (logger.isTraceEnabled()) {
logger.trace("rollershutterPositionStop: rollershutter {} executing", id);
}
cancelRollershutterStop();
rollershutterTask = null;
filterEvent = false;
waitForEvent = true;
executeRollershutterStop();
}
private void executeRollershutterTask() {
if (logger.isTraceEnabled()) {
logger.trace("executeRollershutterTask: rollershutter {} task triggered", id);
}
waitForEvent = false;
Action action = rollershutterTask;
if (action != null) {
action.execute();
rollershutterTask = null;
}
}
/**
* Method used to schedule a rollershutter stop when moving. This allows stopping the rollershutter at a percent
* position.
*
* @param currentValue current percent position
* @param newValue new percent position
*
*/
private void scheduleRollershutterStop(int currentValue, int newValue) {
// filter first event for a rollershutter coming from Niko Home Control if moving to an intermediate
// position to avoid updating state to full open or full close
filterEvent = true;
long duration = rollershutterMoveTime(currentValue, newValue);
setRollershutterMovingTrue(duration);
if (logger.isTraceEnabled()) {
logger.trace("scheduleRollershutterStop: schedule rollershutter {} stop in {}ms", id, duration);
}
rollershutterStopTask = scheduler.schedule(() -> {
logger.trace("scheduleRollershutterStop: run rollershutter {} stop", id);
executeRollershutterStop();
}, duration, TimeUnit.MILLISECONDS);
}
private void cancelRollershutterStop() {
ScheduledFuture<?> stopTask = rollershutterStopTask;
if (stopTask != null) {
if (logger.isTraceEnabled()) {
logger.trace("cancelRollershutterStop: cancel rollershutter {} stop", id);
}
stopTask.cancel(true);
}
rollershutterStopTask = null;
filterEvent = false;
}
private void setRollershutterMovingTrue(long duration) {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingTrue: rollershutter {} moving", id);
}
rollershutterMoving = true;
rollershutterMovingFlagTask = scheduler.schedule(() -> {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingTrue: rollershutter {} stopped moving", id);
}
rollershutterMoving = false;
}, duration, TimeUnit.MILLISECONDS);
}
private void setRollershutterMovingFalse() {
if (logger.isTraceEnabled()) {
logger.trace("setRollershutterMovingFalse: rollershutter {} not moving", id);
}
rollershutterMoving = false;
ScheduledFuture<?> future = rollershutterMovingFlagTask;
if (future != null) {
future.cancel(true);
rollershutterMovingFlagTask = null;
}
}
private long rollershutterMoveTime(int currentValue, int newValue) {
int totalTime = (newValue > currentValue) ? getOpenTime() : getCloseTime();
long duration = Math.abs(newValue - currentValue) * totalTime * 10;
if (logger.isTraceEnabled()) {
logger.trace("rollershutterMoveTime: rollershutter {} move time {}", id, duration);
}
return duration;
}
}

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.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcLocation1} class represents the location Niko Home Control communication object. It contains all fields
* representing a Niko Home Control location.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public final class NhcLocation1 {
private final String name;
public NhcLocation1(String name) {
this.name = name;
}
public String getName() {
return name;
}
}

View File

@@ -0,0 +1,44 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
/**
* Class {@link NhcMessageBase1} used as base class for output from gson for cmd or event feedback from Niko Home
* Control. This class only contains the common base fields required for the deserializer
* {@link NikoHomeControlMessageDeserializer1} to select the specific formats implemented in {@link NhcMessageMap1},
* {@link NhcMessageListMap1}, {@link NhcMessageCmd1}.
* <p>
*
* @author Mark Herwege - Initial Contribution
*/
abstract class NhcMessageBase1 {
private String cmd;
private String event;
String getCmd() {
return cmd;
}
void setCmd(String cmd) {
this.cmd = cmd;
}
String getEvent() {
return event;
}
void setEvent(String event) {
this.event = event;
}
}

View File

@@ -0,0 +1,68 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
/**
* Class {@link NhcMessageCmd1} used as input to gson to send commands to Niko Home Control. Extends
* {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"executeactions","id":1,"value1":0}</code>
*
* @author Mark Herwege - Initial Contribution
*/
@SuppressWarnings("unused")
class NhcMessageCmd1 extends NhcMessageBase1 {
private int id;
private int value1;
private int value2;
private int value3;
private int mode;
private int overrule;
private String overruletime;
NhcMessageCmd1(String cmd) {
super.setCmd(cmd);
}
NhcMessageCmd1(String cmd, int id) {
this(cmd);
this.id = id;
}
NhcMessageCmd1(String cmd, int id, int value1) {
this(cmd, id);
this.value1 = value1;
}
NhcMessageCmd1(String cmd, int id, int value1, int value2, int value3) {
this(cmd, id, value1);
this.value2 = value2;
this.value3 = value3;
}
NhcMessageCmd1 withMode(int mode) {
this.mode = mode;
return this;
}
NhcMessageCmd1 withOverrule(int overrule) {
this.overrule = overrule;
return this;
}
NhcMessageCmd1 withOverruletime(String overruletime) {
this.overruletime = overruletime;
return this;
}
}

View File

@@ -0,0 +1,39 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Class {@link NhcMessageListMap1} used as output from gson for cmd or event feedback from Niko Home Control where the
* data part is enclosed by [] and contains a list of json strings. Extends {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"listactions","data":[{"id":1,"name":"Garage","type":1,"location":1,"value1":0},
* {"id":25,"name":"Frontdoor","type":2,"location":2,"value1":0}]}</code>
*
* @author Mark Herwege - Initial Contribution
*/
class NhcMessageListMap1 extends NhcMessageBase1 {
private List<Map<String, String>> data = new ArrayList<>();
List<Map<String, String>> getData() {
return data;
}
void setData(List<Map<String, String>> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import java.util.HashMap;
import java.util.Map;
/**
* Class {@link NhcMessageMap1} used as output from gson for cmd or event feedback from Niko Home Control where the
* data part is a simple json string. Extends {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"executeactions", "data":{"error":0}}</code>
*
* @author Mark Herwege - Initial Contribution
*/
class NhcMessageMap1 extends NhcMessageBase1 {
private Map<String, String> data = new HashMap<>();
Map<String, String> getData() {
return data;
}
void setData(Map<String, String> data) {
this.data = data;
}
}

View File

@@ -0,0 +1,116 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link NhcSystemInfo1} class represents the systeminfo Niko Home Control communication object. It contains all
* Niko Home Control system data received from the Niko Home Control controller when initializing the connection.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public final class NhcSystemInfo1 {
private String swVersion = "";
private String api = "";
private String time = "";
private String language = "";
private String currency = "";
private String units = "";
private String dst = "";
private String tz = "";
private String lastEnergyErase = "";
private String lastConfig = "";
public String getSwVersion() {
return swVersion;
}
void setSwVersion(String swVersion) {
this.swVersion = swVersion;
}
public String getApi() {
return api;
}
void setApi(String api) {
this.api = api;
}
public String getTime() {
return time;
}
void setTime(String time) {
this.time = time;
}
public String getLanguage() {
return language;
}
void setLanguage(String language) {
this.language = language;
}
public String getCurrency() {
return currency;
}
void setCurrency(String currency) {
this.currency = currency;
}
public String getUnits() {
return units;
}
void setUnits(String units) {
this.units = units;
}
public String getDst() {
return dst;
}
void setDst(String dst) {
this.dst = dst;
}
public String getTz() {
return tz;
}
void setTz(String tz) {
this.tz = tz;
}
public String getLastEnergyErase() {
return lastEnergyErase;
}
void setLastEnergyErase(String lastEnergyErase) {
this.lastEnergyErase = lastEnergyErase;
}
public String getLastConfig() {
return lastConfig;
}
void setLastConfig(String lastConfig) {
this.lastConfig = lastConfig;
}
}

View File

@@ -0,0 +1,63 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat1} class represents the thermostat Niko Home Control I communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcThermostat1 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat1.class);
NhcThermostat1(String id, String name, @Nullable String location, NikoHomeControlCommunication nhcComm) {
super(id, name, location, nhcComm);
}
/**
* Sends thermostat mode to Niko Home Control.
*
* @param mode
*/
@Override
public void executeMode(int mode) {
logger.debug("Niko Home Control: execute thermostat mode {} for {}", mode, id);
nhcComm.executeThermostat(id, Integer.toString(mode));
}
/**
* Sends thermostat setpoint to Niko Home Control.
*
* @param overrule temperature to overrule the setpoint in 0.1°C multiples
* @param time time duration in min for overrule
*/
@Override
public void executeOverrule(int overrule, int overruletime) {
logger.debug("Niko Home Control: execute thermostat overrule {} during {} min for {}", overrule, overruletime,
id);
nhcComm.executeThermostat(id, overrule, overruletime);
}
}

View File

@@ -0,0 +1,538 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
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 java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
/**
* The {@link NikoHomeControlCommunication1} class is able to do the following tasks with Niko Home Control I
* systems:
* <ul>
* <li>Start and stop TCP socket connection with Niko Home Control IP-interface.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen to events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication {
private Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication1.class);
private final NhcSystemInfo1 systemInfo = new NhcSystemInfo1();
private final Map<String, NhcLocation1> locations = new ConcurrentHashMap<>();
private @Nullable Socket nhcSocket;
private @Nullable PrintWriter nhcOut;
private @Nullable BufferedReader nhcIn;
private volatile boolean listenerStopped;
private volatile boolean nhcEventsRunning;
private ScheduledExecutorService scheduler;
// We keep only 2 gson adapters used to serialize and deserialize all messages sent and received
protected final Gson gsonOut = new Gson();
protected Gson gsonIn;
/**
* Constructor for Niko Home Control I communication object, manages communication with
* Niko Home Control IP-interface.
*
*/
public NikoHomeControlCommunication1(NhcControllerEvent handler, ScheduledExecutorService scheduler) {
super(handler);
this.scheduler = scheduler;
// When we set up this object, we want to get the proper gson adapter set up once
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(NhcMessageBase1.class, new NikoHomeControlMessageDeserializer1());
gsonIn = gsonBuilder.create();
}
@Override
public synchronized void startCommunication() {
try {
for (int i = 1; nhcEventsRunning && (i <= 5); i++) {
// the events listener thread did not finish yet, so wait max 5000ms before restarting
Thread.sleep(1000);
}
if (nhcEventsRunning) {
logger.debug("Niko Home Control: starting but previous connection still active after 5000ms");
throw new IOException();
}
InetAddress addr = handler.getAddr();
int port = handler.getPort();
Socket socket = new Socket(addr, port);
nhcSocket = socket;
nhcOut = new PrintWriter(socket.getOutputStream(), true);
nhcIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
logger.debug("Niko Home Control: connected via local port {}", socket.getLocalPort());
// initialize all info in local fields
initialize();
// Start Niko Home Control event listener. This listener will act on all messages coming from
// IP-interface.
(new Thread(this::runNhcEvents)).start();
} catch (IOException | InterruptedException e) {
logger.warn("Niko Home Control: error initializing communication");
stopCommunication();
handler.controllerOffline();
}
}
/**
* Cleanup socket when the communication with Niko Home Control IP-interface is closed.
*
*/
@Override
public synchronized void stopCommunication() {
listenerStopped = true;
Socket socket = nhcSocket;
if (socket != null) {
try {
socket.close();
} catch (IOException ignore) {
// ignore IO Error when trying to close the socket if the intention is to close it anyway
}
}
nhcSocket = null;
logger.debug("Niko Home Control: communication stopped");
}
@Override
public boolean communicationActive() {
return (nhcSocket != null);
}
/**
* Method that handles inbound communication from Niko Home Control, to be called on a separate thread.
* <p>
* The thread listens to the TCP socket opened at instantiation of the {@link NikoHomeControlCommunication} 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 runNhcEvents() {
String nhcMessage;
logger.debug("Niko Home Control: listening for events");
listenerStopped = false;
nhcEventsRunning = true;
try {
while (!listenerStopped & (nhcIn != null) & ((nhcMessage = nhcIn.readLine()) != null)) {
readMessage(nhcMessage);
}
} catch (IOException e) {
if (!listenerStopped) {
nhcEventsRunning = false;
// this is a socket error, not a communication stop triggered from outside this runnable
logger.warn("Niko Home Control: IO error in listener");
// the IO has stopped working, so we need to close cleanly and try to restart
restartCommunication();
return;
}
} finally {
nhcEventsRunning = false;
}
nhcEventsRunning = false;
// this is a stop from outside the runnable, so just log it and stop
logger.debug("Niko Home Control: event listener thread stopped");
}
/**
* After setting up the communication with the Niko Home Control IP-interface, send all initialization messages.
* <p>
* Only at first initialization, also set the return values. Otherwise use the runnable to get updated values.
* While communication is set up for thermostats, tariff data and alarms, only info from locations and actions
* is used beyond this point in openHAB. All other elements are for future extensions.
*
* @throws IOException
*/
private void initialize() throws IOException {
sendAndReadMessage("systeminfo");
sendAndReadMessage("startevents");
sendAndReadMessage("listlocations");
sendAndReadMessage("listactions");
sendAndReadMessage("listthermostat");
sendAndReadMessage("listthermostatHVAC");
sendAndReadMessage("readtariffdata");
sendAndReadMessage("getalarms");
}
@SuppressWarnings("null")
private void sendAndReadMessage(String command) throws IOException {
sendMessage(new NhcMessageCmd1(command));
readMessage(nhcIn.readLine());
}
/**
* Called by other methods to send json cmd to Niko Home Control.
*
* @param nhcMessage
*/
@SuppressWarnings("null")
private synchronized void sendMessage(Object nhcMessage) {
String json = gsonOut.toJson(nhcMessage);
logger.debug("Niko Home Control: send json {}", json);
nhcOut.println(json);
if (nhcOut.checkError()) {
logger.warn("Niko Home Control: error sending message, trying to restart communication");
restartCommunication();
// retry sending after restart
logger.debug("Niko Home Control: resend json {}", json);
nhcOut.println(json);
if (nhcOut.checkError()) {
logger.warn("Niko Home Control: error resending message");
handler.controllerOffline();
}
}
}
/**
* Method that interprets all feedback from Niko Home Control and calls appropriate handling methods.
*
* @param nhcMessage message read from Niko Home Control.
*/
private void readMessage(@Nullable String nhcMessage) {
logger.debug("Niko Home Control: received json {}", nhcMessage);
try {
NhcMessageBase1 nhcMessageGson = gsonIn.fromJson(nhcMessage, NhcMessageBase1.class);
String cmd = nhcMessageGson.getCmd();
String event = nhcMessageGson.getEvent();
if ("systeminfo".equals(cmd)) {
cmdSystemInfo(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("startevents".equals(cmd)) {
cmdStartEvents(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("listlocations".equals(cmd)) {
cmdListLocations(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("listactions".equals(cmd)) {
cmdListActions(((NhcMessageListMap1) nhcMessageGson).getData());
} else if (("listthermostat").equals(cmd)) {
cmdListThermostat(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("executeactions".equals(cmd)) {
cmdExecuteActions(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("executethermostat".equals(cmd)) {
cmdExecuteThermostat(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("listactions".equals(event)) {
eventListActions(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("listthermostat".equals(event)) {
eventListThermostat(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("getalarms".equals(event)) {
eventGetAlarms(((NhcMessageMap1) nhcMessageGson).getData());
} else {
logger.debug("Niko Home Control: not acted on json {}", nhcMessage);
}
} catch (JsonParseException e) {
logger.debug("Niko Home Control: not acted on unsupported json {}", nhcMessage);
}
}
private synchronized void cmdSystemInfo(Map<String, String> data) {
logger.debug("Niko Home Control: systeminfo");
if (data.containsKey("swversion")) {
systemInfo.setSwVersion(data.get("swversion"));
}
if (data.containsKey("api")) {
systemInfo.setApi(data.get("api"));
}
if (data.containsKey("time")) {
systemInfo.setTime(data.get("time"));
}
if (data.containsKey("language")) {
systemInfo.setLanguage(data.get("language"));
}
if (data.containsKey("currency")) {
systemInfo.setCurrency(data.get("currency"));
}
if (data.containsKey("units")) {
systemInfo.setUnits(data.get("units"));
}
if (data.containsKey("DST")) {
systemInfo.setDst(data.get("DST"));
}
if (data.containsKey("TZ")) {
systemInfo.setTz(data.get("TZ"));
}
if (data.containsKey("lastenergyerase")) {
systemInfo.setLastEnergyErase(data.get("lastenergyerase"));
}
if (data.containsKey("lastconfig")) {
systemInfo.setLastConfig(data.get("lastconfig"));
}
}
/**
* Return the object with system info as read from the Niko Home Control controller.
*
* @return the systemInfo
*/
public synchronized NhcSystemInfo1 getSystemInfo() {
return systemInfo;
}
private void cmdStartEvents(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: start events success");
} else {
logger.warn("Niko Home Control: error code {} returned on start events", errorCode);
}
}
private void cmdListLocations(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list locations");
locations.clear();
for (Map<String, String> location : data) {
String id = location.get("id");
String name = location.get("name");
NhcLocation1 nhcLocation1 = new NhcLocation1(name);
locations.put(id, nhcLocation1);
}
}
private void cmdListActions(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list actions");
for (Map<String, String> action : data) {
String id = action.get("id");
int state = Integer.parseInt(action.get("value1"));
String value2 = action.get("value2");
int closeTime = ((value2 == null) || value2.isEmpty() ? 0 : Integer.parseInt(value2));
String value3 = action.get("value3");
int openTime = ((value3 == null) || value3.isEmpty() ? 0 : Integer.parseInt(value3));
if (!actions.containsKey(id)) {
// Initial instantiation of NhcAction class for action object
String name = action.get("name");
String type = action.get("type");
ActionType actionType = ActionType.GENERIC;
switch (type) {
case "0":
actionType = ActionType.TRIGGER;
break;
case "1":
actionType = ActionType.RELAY;
break;
case "2":
actionType = ActionType.DIMMER;
break;
case "4":
case "5":
actionType = ActionType.ROLLERSHUTTER;
break;
default:
logger.debug("Niko Home Control: unknown action type {} for action {}", type, id);
continue;
}
String locationId = action.get("location");
String location = "";
if (!locationId.isEmpty()) {
location = locations.get(locationId).getName();
}
NhcAction nhcAction = new NhcAction1(id, name, actionType, location, this, scheduler);
if (actionType == ActionType.ROLLERSHUTTER) {
nhcAction.setShutterTimes(openTime, closeTime);
}
nhcAction.setState(state);
actions.put(id, nhcAction);
} else {
// Action object already exists, so only update state.
// If we would re-instantiate action, we would lose pointer back from action to thing handler that was
// set in thing handler initialize().
actions.get(id).setState(state);
}
}
}
private void cmdListThermostat(List<Map<String, String>> data) {
logger.debug("Niko Home Control: list thermostats");
for (Map<String, String> thermostat : data) {
String id = thermostat.get("id");
int measured = Integer.parseInt(thermostat.get("measured"));
int setpoint = Integer.parseInt(thermostat.get("setpoint"));
int mode = Integer.parseInt(thermostat.get("mode"));
int overrule = Integer.parseInt(thermostat.get("overrule"));
// overruletime received in "HH:MM" format
String[] overruletimeStrings = thermostat.get("overruletime").split(":");
int overruletime = 0;
if (overruletimeStrings.length == 2) {
overruletime = Integer.parseInt(overruletimeStrings[0]) * 60 + Integer.parseInt(overruletimeStrings[1]);
}
int ecosave = Integer.parseInt(thermostat.get("ecosave"));
// For parity with NHC II, assume heating/cooling if thermostat is on and setpoint different from measured
int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0;
if (!thermostats.containsKey(id)) {
// Initial instantiation of NhcThermostat class for thermostat object
String name = thermostat.get("name");
String locationId = thermostat.get("location");
String location = "";
if (!locationId.isEmpty()) {
location = locations.get(locationId).getName();
}
NhcThermostat nhcThermostat = new NhcThermostat1(id, name, location, this);
nhcThermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostats.put(id, nhcThermostat);
} else {
// Thermostat object already exists, so only update state.
// If we would re-instantiate thermostat, we would lose pointer back from thermostat to thing handler
// that was set in thing handler initialize().
thermostats.get(id).updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
}
}
private void cmdExecuteActions(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: execute action success");
} else {
logger.warn("Niko Home Control: error code {} returned on command execution", errorCode);
}
}
private void cmdExecuteThermostat(Map<String, String> data) {
int errorCode = Integer.parseInt(data.get("error"));
if (errorCode == 0) {
logger.debug("Niko Home Control: execute thermostats success");
} else {
logger.warn("Niko Home Control: error code {} returned on command execution", errorCode);
}
}
private void eventListActions(List<Map<String, String>> data) {
for (Map<String, String> action : data) {
String id = action.get("id");
if (!actions.containsKey(id)) {
logger.warn("Niko Home Control: action in controller not known {}", id);
return;
}
int state = Integer.parseInt(action.get("value1"));
logger.debug("Niko Home Control: event execute action {} with state {}", id, state);
actions.get(id).setState(state);
}
}
private void eventListThermostat(List<Map<String, String>> data) {
for (Map<String, String> thermostat : data) {
String id = thermostat.get("id");
if (!thermostats.containsKey(id)) {
logger.warn("Niko Home Control: thermostat in controller not known {}", id);
return;
}
int measured = Integer.parseInt(thermostat.get("measured"));
int setpoint = Integer.parseInt(thermostat.get("setpoint"));
int mode = Integer.parseInt(thermostat.get("mode"));
int overrule = Integer.parseInt(thermostat.get("overrule"));
// overruletime received in "HH:MM" format
String[] overruletimeStrings = thermostat.get("overruletime").split(":");
int overruletime = 0;
if (overruletimeStrings.length == 2) {
overruletime = Integer.parseInt(overruletimeStrings[0]) * 60 + Integer.parseInt(overruletimeStrings[1]);
}
int ecosave = Integer.parseInt(thermostat.get("ecosave"));
int demand = (mode != 3) ? (setpoint > measured ? 1 : (setpoint < measured ? -1 : 0)) : 0;
logger.debug(
"Niko Home Control: event execute thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
id, measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostats.get(id).updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
}
private void eventGetAlarms(Map<String, String> data) {
int type = Integer.parseInt(data.get("type"));
String alarmText = data.get("text");
switch (type) {
case 0:
logger.debug("Niko Home Control: alarm - {}", alarmText);
handler.alarmEvent(alarmText);
break;
case 1:
logger.debug("Niko Home Control: notice - {}", alarmText);
handler.noticeEvent(alarmText);
break;
default:
logger.debug("Niko Home Control: unexpected message type {}", type);
}
}
@Override
public void executeAction(String actionId, String value) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executeactions", Integer.parseInt(actionId),
Integer.parseInt(value));
sendMessage(nhcCmd);
}
@Override
public void executeThermostat(String thermostatId, String mode) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withMode(Integer.parseInt(mode));
sendMessage(nhcCmd);
}
@Override
public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
String overruletimeString = String.format("%1$02d:%2$02d", overruleTime / 60, overruleTime % 60);
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withOverrule(overruleTemp).withOverruletime(overruletimeString);
sendMessage(nhcCmd);
}
}

View File

@@ -0,0 +1,102 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc1;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* Class {@link NikoHomeControlMessageDeserializer1} deserializes all json messages from Niko Home Control. Various json
* message formats are supported. The format is selected based on the content of the cmd and event json objects.
*
* @author Mark Herwege - Initial Contribution
*
*/
class NikoHomeControlMessageDeserializer1 implements JsonDeserializer<NhcMessageBase1> {
@Override
public NhcMessageBase1 deserialize(final JsonElement json, final Type typeOfT,
final JsonDeserializationContext context) throws JsonParseException {
final JsonObject jsonObject = json.getAsJsonObject();
try {
String cmd = null;
String event = null;
if (jsonObject.has("cmd")) {
cmd = jsonObject.get("cmd").getAsString();
}
if (jsonObject.has("event")) {
event = jsonObject.get("event").getAsString();
}
JsonElement jsonData = null;
if (jsonObject.has("data")) {
jsonData = jsonObject.get("data");
}
NhcMessageBase1 message = null;
if (jsonData != null) {
if (jsonData.isJsonObject()) {
message = new NhcMessageMap1();
Map<String, String> data = new HashMap<>();
for (Entry<String, JsonElement> entry : jsonData.getAsJsonObject().entrySet()) {
data.put(entry.getKey(), entry.getValue().getAsString());
}
((NhcMessageMap1) message).setData(data);
} else if (jsonData.isJsonArray()) {
JsonArray jsonDataArray = jsonData.getAsJsonArray();
message = new NhcMessageListMap1();
List<Map<String, String>> dataList = new ArrayList<>();
for (int i = 0; i < jsonDataArray.size(); i++) {
JsonObject jsonDataObject = jsonDataArray.get(i).getAsJsonObject();
Map<String, String> data = new HashMap<>();
for (Entry<String, JsonElement> entry : jsonDataObject.entrySet()) {
data.put(entry.getKey(), entry.getValue().getAsString());
}
dataList.add(data);
}
((NhcMessageListMap1) message).setData(dataList);
}
}
if (message != null) {
message.setCmd(cmd);
message.setEvent(event);
} else {
throw new JsonParseException("Unexpected Json type");
}
return message;
} catch (IllegalStateException | ClassCastException e) {
throw new JsonParseException("Unexpected Json type");
}
}
}

View File

@@ -0,0 +1,136 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAction2} class represents the action Niko Home Control II communication object. It contains all fields
* representing a Niko Home Control action and has methods to trigger the action in Niko Home Control and receive action
* updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcAction2 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction2.class);
private volatile boolean booleanState;
private String model;
private String technology;
NhcAction2(String id, String name, String model, String technology, ActionType type, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
super(id, name, type, location, nhcComm);
this.model = model;
this.technology = technology;
}
/**
* Get on/off state of action.
* <p>
* true for on, false for off
*
* @return action on/off state
*/
boolean booleanState() {
return booleanState;
}
@Override
public int getState() {
return booleanState ? state : 0;
}
/**
* Sets on/off state of action.
*
* @param state - boolean false for on, true for off
*/
public void setBooleanState(boolean state) {
booleanState = state;
if (getType().equals(ActionType.DIMMER)) {
if (booleanState) {
// only send stored brightness value if on
updateState();
} else {
updateState(0);
}
} else {
if (booleanState) {
this.state = 100;
updateState(100);
} else {
this.state = 0;
updateState(0);
}
}
}
/**
* Sets state of action. This version is used for Niko Home Control II.
*
* @param state - The allowed values depend on the action type.
* switch action: 0 or 100
* dimmer action: between 0 and 100
* rollershutter action: between 0 and 100
*/
@Override
public void setState(int state) {
this.state = state;
if (getType().equals(ActionType.DIMMER)) { // for dimmers, only send the update to the event
// handler if on
if (booleanState) {
updateState();
}
} else {
updateState();
}
}
/**
* Sends action to Niko Home Control. This version is used for Niko Home Control II, that has extra status options.
*
* @param command - The allowed values depend on the action type.
* switch action: On or Off
* dimmer action: between 0 and 100, On or Off
* rollershutter action: between 0 and 100, Up, Down or Stop
*/
@Override
public void execute(String command) {
logger.debug("Niko Home Control: execute action {} of type {} for {}", command, type, id);
nhcComm.executeAction(id, command);
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

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.nikohomecontrol.internal.protocol.nhc2;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link NhcDevice2} represents a Niko Home Control II device. It is used when parsing the device response json and
* when creating the state update json to send to the Connected Controller.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcDevice2 {
static class NhcProperty {
// fields for lights
@Nullable
String status;
@Nullable
String brightness;
@Nullable
String aligned;
@Nullable
String basicState;
// fields for motors
@Nullable
String action;
@Nullable
String position;
@Nullable
String moving;
// fields for thermostats and hvac
@Nullable
String setpointTemperature;
@Nullable
String program;
@Nullable
String overruleActive;
@Nullable
String overruleSetpoint;
@Nullable
String overruleTime;
@Nullable
String ecoSave;
@Nullable
String demand;
@Nullable
String operationMode;
@Nullable
String ambientTemperature;
@Nullable
String protectMode;
@Nullable
String thermostatOn;
@Nullable
String hvacOn;
// fields for fans and ventilation
@Nullable
String fanSpeed;
// fields for electricity metering
@Nullable
String electricalEnergy;
@Nullable
String electricalPower;
@Nullable
String reportInstantUsage;
// fields for access control
@Nullable
String doorlock;
}
static class NhcTrait {
@Nullable
String macAddress;
// fields for energyMeters metering
@Nullable
String channel;
@Nullable
String meterType;
}
static class NhcParameter {
@Nullable
String locationId;
@Nullable
String locationName;
@Nullable
String locationIcon;
// fields for electricity metering
@Nullable
String flow;
@Nullable
String segment;
@Nullable
String clampType;
@Nullable
String shortName;
}
String name = "";
String uuid = "";
String technology = "";
String identifier = "";
String model = "";
String type = "";
String online = "";
@Nullable
List<NhcProperty> properties;
@Nullable
List<NhcTrait> traits;
@Nullable
List<NhcParameter> parameters;
}

View File

@@ -0,0 +1,90 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import java.util.concurrent.ScheduledExecutorService;
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.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
/**
* The {@link NhcEnergyMeter} class represents the energyMeters metering Niko Home Control communication object. It
* contains all fields representing a Niko Home Control energyMeters meter and has methods to receive energyMeters usage
* information.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcEnergyMeter2 extends NhcEnergyMeter {
private ScheduledExecutorService scheduler;
private volatile @Nullable ScheduledFuture<?> restartTimer;
private String model;
private String technology;
protected NhcEnergyMeter2(String id, String name, String model, String technology,
NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
super(id, name, nhcComm);
this.model = model;
this.technology = technology;
this.scheduler = scheduler;
}
/**
* Start the flow from energy information from the energy meter. The Niko Home Control energy meter will send power
* information every 2s for 30s. This method will retrigger every 25s to make sure the information continues
* flowing. If the information is no longer required, make sure to use the {@link stopEnergyMeter} method to stop
* the flow of information.
*
* @param topic topic the start event will have to be sent to every 25s
* @param gsonMessage content of message
*/
public void startEnergyMeter(String topic, String gsonMessage) {
stopEnergyMeter();
restartTimer = scheduler.scheduleWithFixedDelay(() -> {
((NikoHomeControlCommunication2) nhcComm).executeEnergyMeter(topic, gsonMessage);
}, 0, 25, TimeUnit.SECONDS);
}
/**
* Cancel receiving energy information from the controller. We therefore stop the automatic retriggering of the
* subscription, see {@link startEnergyMeter}.
*/
public void stopEnergyMeter() {
ScheduledFuture<?> timer = restartTimer;
if (timer != null) {
timer.cancel(true);
restartTimer = null;
}
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

View File

@@ -0,0 +1,47 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* {@link NhcMessage2} represents a Niko Home Control II message. It is used when sending messages to the Connected
* Controller or when parsing the message response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcMessage2 {
static class NhcMessageParam {
@Nullable
List<NhcSystemInfo2> systemInfo;
@Nullable
List<NhcService2> services;
@Nullable
List<NhcDevice2> devices;
@Nullable
List<NhcNotification2> notifications;
@Nullable
List<NhcTimeInfo2> timeInfo;
}
@Nullable
String method;
String errCode = "";
String errMessage = "";
@Nullable
List<NhcMessageParam> params;
}

View File

@@ -0,0 +1,247 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.io.transport.mqtt.MqttActionCallback;
import org.openhab.core.io.transport.mqtt.MqttBrokerConnection;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttException;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link NhcMqttConnection2} manages the MQTT connection to the Connected Controller. It allows receiving state
* information about specific devices and sending updates to specific devices.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcMqttConnection2 implements MqttActionCallback {
private final Logger logger = LoggerFactory.getLogger(NhcMqttConnection2.class);
private volatile @Nullable MqttBrokerConnection mqttConnection;
private volatile @Nullable CompletableFuture<Boolean> subscribedFuture;
private volatile @Nullable CompletableFuture<Boolean> stoppedFuture;
private MqttMessageSubscriber messageSubscriber;
private MqttConnectionObserver connectionObserver;
private TrustManager trustManagers[];
private String clientId;
private volatile String cocoAddress = "";
private volatile int port;
private volatile String profile = "";
private volatile String token = "";
NhcMqttConnection2(String clientId, MqttMessageSubscriber messageSubscriber,
MqttConnectionObserver connectionObserver) throws CertificateException {
trustManagers = getTrustManagers();
this.clientId = clientId;
this.messageSubscriber = messageSubscriber;
this.connectionObserver = connectionObserver;
}
private TrustManager[] getTrustManagers() throws CertificateException {
ResourceBundle certificatesBundle = ResourceBundle.getBundle("nikohomecontrol/certificates");
try {
// Load server public certificates into key store
CertificateFactory cf = CertificateFactory.getInstance("X509");
InputStream certificateStream;
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
for (String certName : certificatesBundle.keySet()) {
certificateStream = new ByteArrayInputStream(
certificatesBundle.getString(certName).getBytes(StandardCharsets.UTF_8));
X509Certificate certificate = (X509Certificate) cf.generateCertificate(certificateStream);
keyStore.setCertificateEntry(certName, certificate);
}
ResourceBundle.clearCache();
// Create trust managers used to validate server
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmFactory.init(keyStore);
return tmFactory.getTrustManagers();
} catch (CertificateException | KeyStoreException | NoSuchAlgorithmException | IOException e) {
logger.warn("Niko Home Control: error with SSL context creation: {} ", e.getMessage());
throw new CertificateException("SSL context creation exception", e);
} finally {
ResourceBundle.clearCache();
}
}
/**
* Start a secure MQTT connection and subscribe to all topics.
*
* @param subscriber MqttMessageSubscriber that will handle received messages
* @param cocoAddress IP Address of the Niko Connected Controller
* @param port Port for MQTT communication with the Niko Connected Controller
* @param token JWT token for the hobby profile
* @throws MqttException
*/
synchronized void startConnection(String cocoAddress, int port, String profile, String token) throws MqttException {
CompletableFuture<Boolean> future = stoppedFuture;
if (future != null) {
try {
future.get(5000, TimeUnit.MILLISECONDS);
logger.debug("Niko Home Control: finished stopping connection");
} catch (InterruptedException | ExecutionException | TimeoutException ignore) {
logger.debug("Niko Home Control: error stopping connection");
}
stoppedFuture = null;
}
logger.debug("Niko Home Control: starting connection...");
this.cocoAddress = cocoAddress;
this.port = port;
this.profile = profile;
this.token = token;
MqttBrokerConnection connection = createMqttConnection();
connection.addConnectionObserver(connectionObserver);
mqttConnection = connection;
try {
if (connection.start().get(5000, TimeUnit.MILLISECONDS)) {
if (subscribedFuture == null) {
subscribedFuture = connection.subscribe("#", messageSubscriber);
}
} else {
logger.debug("Niko Home Control: error connecting");
throw new MqttException("Connection execution exception");
}
} catch (InterruptedException e) {
logger.debug("Niko Home Control: connection interrupted exception");
throw new MqttException("Connection interrupted exception");
} catch (ExecutionException e) {
logger.debug("Niko Home Control: connection execution exception", e.getCause());
throw new MqttException("Connection execution exception");
} catch (TimeoutException e) {
logger.debug("Niko Home Control: connection timeout exception");
throw new MqttException("Connection timeout exception");
}
}
private MqttBrokerConnection createMqttConnection() throws MqttException {
MqttBrokerConnection connection = new MqttBrokerConnection(cocoAddress, port, true, clientId);
connection.setTrustManagers(trustManagers);
connection.setCredentials(profile, token);
connection.setQos(1);
return connection;
}
/**
* Stop the MQTT connection.
*/
void stopConnection() {
logger.debug("Niko Home Control: stopping connection...");
MqttBrokerConnection connection = mqttConnection;
if (connection != null) {
connection.removeConnectionObserver(connectionObserver);
}
stoppedFuture = stopConnection(connection);
mqttConnection = null;
CompletableFuture<Boolean> future = subscribedFuture;
if (future != null) {
future.complete(false);
subscribedFuture = null;
}
}
private CompletableFuture<Boolean> stopConnection(@Nullable MqttBrokerConnection connection) {
if (connection != null) {
return connection.stop();
} else {
return CompletableFuture.completedFuture(true);
}
}
/**
* @return true if connection established and subscribed to all topics
*/
private boolean isConnected() {
MqttBrokerConnection connection = mqttConnection;
CompletableFuture<Boolean> future = subscribedFuture;
if (connection != null) {
try {
if ((future != null) && future.get(5000, TimeUnit.MILLISECONDS)) {
MqttConnectionState state = connection.connectionState();
logger.debug("Niko Home Control: connection state {} for {}", state, connection.getClientId());
return state == MqttConnectionState.CONNECTED;
}
} catch (InterruptedException | ExecutionException | TimeoutException e) {
return false;
}
}
return false;
}
/**
* Publish a message on the general connection.
*
* @param topic
* @param payload
* @throws MqttException
*/
void connectionPublish(String topic, String payload) throws MqttException {
MqttBrokerConnection connection = mqttConnection;
if (connection == null) {
logger.debug("Niko Home Control: cannot publish, no connection");
throw new MqttException("No connection exception");
}
if (isConnected()) {
logger.debug("Niko Home Control: publish {}, {}", topic, payload);
connection.publish(topic, payload.getBytes());
} else {
logger.debug("Niko Home Control: cannot publish, not subscribed to connection messages");
}
}
@Override
public void onSuccess(String topic) {
logger.debug("Niko Home Control: publish succeeded {}", topic);
}
@Override
public void onFailure(String topic, Throwable error) {
logger.debug("Niko Home Control: publish failed {}, {}", topic, error.getMessage(), error);
}
}

View File

@@ -0,0 +1,30 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NhcNotification2} represents a Niko Home Control II notification. It is used when parsing the notification
* response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcNotification2 {
String status = "";
String type = "";
String timeOccured = "";
String uuid = "";
String text = "";
}

View File

@@ -0,0 +1,29 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NhcService2} represents a Niko Home Control II service. It is used when parsing the service response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcService2 {
String name = "";
String name() {
return name;
}
}

View File

@@ -0,0 +1,106 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import java.util.ArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* {@link NhcSystemInfo2} represents Niko Home Control II system info. It is used when parsing the systeminfo response
* json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcSystemInfo2 {
static class NhcSwVersion {
String nhcVersion = "";
String cocoImage = "";
}
String lastConfig = "";
String waterTariff = "";
String electricityTariff = "";
String gasTariff = "";
String currency = "";
String units = "";
String language = "";
@SerializedName(value = "SWversions")
ArrayList<NhcSwVersion> swVersions = new ArrayList<>();
/**
* @return the NhcVersion
*/
public String getNhcVersion() {
return swVersions.stream().map(p -> p.nhcVersion).filter(v -> !v.isEmpty()).findFirst().orElse("");
}
/**
* @return the CocoImage version
*/
public String getCocoImage() {
return swVersions.stream().map(p -> p.cocoImage).filter(v -> !v.isEmpty()).findFirst().orElse("");
}
/**
* @return the lastConfig
*/
public String getLastConfig() {
return lastConfig;
}
/**
* @return the waterTariff
*/
public String getWaterTariff() {
return waterTariff;
}
/**
* @return the electricityTariff
*/
public String getElectricityTariff() {
return electricityTariff;
}
/**
* @return the gasTariff
*/
public String getGasTariff() {
return gasTariff;
}
/**
* @return the currency
*/
public String getCurrency() {
return currency;
}
/**
* @return the units
*/
public String getUnits() {
return units;
}
/**
* @return the language
*/
public String getLanguage() {
return language;
}
}

View File

@@ -0,0 +1,75 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.THERMOSTATMODES;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcThermostat2} class represents the thermostat Niko Home Control II communication object. It contains all
* fields representing a Niko Home Control thermostat and has methods to set the thermostat in Niko Home Control and
* receive thermostat updates.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcThermostat2 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat2.class);
private String model;
private String technology;
protected NhcThermostat2(String id, String name, String model, String technology, @Nullable String location,
NikoHomeControlCommunication nhcComm) {
super(id, name, location, nhcComm);
this.model = model;
this.technology = technology;
}
@Override
public void executeMode(int mode) {
logger.debug("Niko Home Control: execute thermostat mode {} for {}", mode, id);
String program = THERMOSTATMODES[mode];
nhcComm.executeThermostat(id, program);
}
@Override
public void executeOverrule(int overrule, int overruletime) {
logger.debug("Niko Home Control: execute thermostat overrule {} during {} min for {}", overrule, overruletime,
id);
nhcComm.executeThermostat(id, overrule, overruletime);
}
/**
* @return model as returned from Niko Home Control
*/
public String getModel() {
return model;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getTechnology() {
return technology;
}
}

View File

@@ -0,0 +1,60 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* {@link NhcTimeInfo2} represents Niko Home Control II timeinfo. It is used when parsing the timeinfo response json.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcTimeInfo2 {
@SerializedName(value = "GMTOffset")
String gmtOffset = "";
String timezone = "";
String isDST = "";
@SerializedName(value = "UTCTime")
String utcTime = "";
/**
* @return the gMTOffset
*/
public String getGMTOffset() {
return gmtOffset;
}
/**
* @return the timeZone
*/
public String getTimezone() {
return timezone;
}
/**
* @return the isDST
*/
public String getIsDst() {
return isDST;
}
/**
* @return the uTCTime
*/
public String getUTCTime() {
return utcTime;
}
}

View File

@@ -0,0 +1,847 @@
/**
* 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.nikohomecontrol.internal.protocol.nhc2;
import static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.*;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcProperty;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMessage2.NhcMessageParam;
import org.openhab.core.io.transport.mqtt.MqttConnectionObserver;
import org.openhab.core.io.transport.mqtt.MqttConnectionState;
import org.openhab.core.io.transport.mqtt.MqttException;
import org.openhab.core.io.transport.mqtt.MqttMessageSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
/**
* The {@link NikoHomeControlCommunication2} class is able to do the following tasks with Niko Home Control II
* systems:
* <ul>
* <li>Start and stop MQTT connection with Niko Home Control II Connected Controller.
* <li>Read all setup and status information from the Niko Home Control Controller.
* <li>Execute Niko Home Control commands.
* <li>Listen for events from Niko Home Control.
* </ul>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
implements MqttMessageSubscriber, MqttConnectionObserver {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlCommunication2.class);
private final NhcMqttConnection2 mqttConnection;
private final List<NhcService2> services = new CopyOnWriteArrayList<>();
private volatile String profile = "";
private volatile @Nullable NhcSystemInfo2 nhcSystemInfo;
private volatile @Nullable NhcTimeInfo2 nhcTimeInfo;
private volatile @Nullable CompletableFuture<Boolean> communicationStarted;
private ScheduledExecutorService scheduler;
private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
/**
* Constructor for Niko Home Control communication object, manages communication with
* Niko Home Control II Connected Controller.
*
* @throws CertificateException when the SSL context for MQTT communication cannot be created
* @throws UnknownHostException when the IP address is not provided
*
*/
public NikoHomeControlCommunication2(NhcControllerEvent handler, String clientId,
ScheduledExecutorService scheduler) throws CertificateException {
super(handler);
mqttConnection = new NhcMqttConnection2(clientId, this, this);
this.scheduler = scheduler;
}
@Override
public synchronized void startCommunication() {
communicationStarted = new CompletableFuture<>();
InetAddress addr = handler.getAddr();
if (addr == null) {
logger.warn("Niko Home Control: IP address cannot be empty");
stopCommunication();
return;
}
String addrString = addr.getHostAddress();
int port = handler.getPort();
logger.debug("Niko Home Control: initializing for mqtt connection to CoCo on {}:{}", addrString, port);
profile = handler.getProfile();
String token = handler.getToken();
if (token.isEmpty()) {
logger.warn("Niko Home Control: JWT token cannot be empty");
stopCommunication();
return;
}
try {
mqttConnection.startConnection(addrString, port, profile, token);
initialize();
} catch (MqttException e) {
logger.warn("Niko Home Control: error in mqtt communication");
stopCommunication();
}
}
@Override
public synchronized void stopCommunication() {
CompletableFuture<Boolean> started = communicationStarted;
if (started != null) {
started.complete(false);
}
communicationStarted = null;
mqttConnection.stopConnection();
}
@Override
public boolean communicationActive() {
CompletableFuture<Boolean> started = communicationStarted;
if (started == null) {
return false;
}
try {
// Wait until we received all devices info to confirm we are active.
return started.get(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
logger.debug("Niko Home Control: exception waiting for connection start");
return false;
}
}
/**
* After setting up the communication with the Niko Home Control Connected Controller, send all initialization
* messages.
*
*/
private void initialize() throws MqttException {
NhcMessage2 message = new NhcMessage2();
message.method = "systeminfo.publish";
mqttConnection.connectionPublish(profile + "/system/cmd", gson.toJson(message));
message.method = "services.list";
mqttConnection.connectionPublish(profile + "/authentication/cmd", gson.toJson(message));
message.method = "devices.list";
mqttConnection.connectionPublish(profile + "/control/devices/cmd", gson.toJson(message));
message.method = "notifications.list";
mqttConnection.connectionPublish(profile + "/notification/cmd", gson.toJson(message));
}
private void connectionLost() {
logger.debug("Niko Home Control: connection lost");
stopCommunication();
handler.controllerOffline();
}
private void systemEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcTimeInfo2> timeInfo = null;
List<NhcSystemInfo2> systemInfo = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
timeInfo = messageParams.stream().filter(p -> (p.timeInfo != null)).findFirst().get().timeInfo;
systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if timeInfo not present in response, this should not happen in a timeInfo response
}
if (timeInfo != null) {
nhcTimeInfo = timeInfo.get(0);
}
if (systemInfo != null) {
nhcSystemInfo = systemInfo.get(0);
handler.updatePropertiesEvent();
}
}
private void systeminfoPublishRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcSystemInfo2> systemInfo = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
systemInfo = messageParams.stream().filter(p -> (p.systemInfo != null)).findFirst().get().systemInfo;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if systemInfo not present in response, this should not happen in a systemInfo response
}
if (systemInfo != null) {
nhcSystemInfo = systemInfo.get(0);
}
}
private void servicesListRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcService2> serviceList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
serviceList = messageParams.stream().filter(p -> (p.services != null)).findFirst().get().services;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if services not present in response, this should not happen in a services response
}
services.clear();
if (serviceList != null) {
services.addAll(serviceList);
}
}
private void devicesListRsp(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcDevice2> deviceList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if devices not present in response, this should not happen in a devices response
}
if (deviceList == null) {
return;
}
for (NhcDevice2 device : deviceList) {
addDevice(device);
updateState(device);
}
// Once a devices list response is received, we know the communication is fully started.
logger.debug("Niko Home Control: Communication start complete.");
handler.controllerOnline();
CompletableFuture<Boolean> future = communicationStarted;
if (future != null) {
future.complete(true);
}
}
private void devicesEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcDevice2> deviceList = null;
String method = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
method = message.method;
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
deviceList = messageParams.stream().filter(p -> (p.devices != null)).findFirst().get().devices;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if devices not present in response, this should not happen in a devices event
}
if (deviceList == null) {
return;
}
if ("devices.removed".equals(method)) {
deviceList.forEach(this::removeDevice);
return;
} else if ("devices.added".equals(method)) {
deviceList.forEach(this::addDevice);
} else if ("devices.changed".contentEquals(method)) {
deviceList.forEach(this::removeDevice);
deviceList.forEach(this::addDevice);
}
deviceList.forEach(this::updateState);
}
private void notificationEvt(String response) {
Type messageType = new TypeToken<NhcMessage2>() {
}.getType();
List<NhcNotification2> notificationList = null;
try {
NhcMessage2 message = gson.fromJson(response, messageType);
List<NhcMessageParam> messageParams = message.params;
if (messageParams != null) {
notificationList = messageParams.stream().filter(p -> (p.notifications != null)).findFirst()
.get().notifications;
}
} catch (JsonSyntaxException e) {
logger.debug("Niko Home Control: unexpected json {}", response);
} catch (NoSuchElementException ignore) {
// Ignore if notifications not present in response, this should not happen in a notifications event
}
logger.debug("Niko Home Control: notifications {}", notificationList);
if (notificationList == null) {
return;
}
for (NhcNotification2 notification : notificationList) {
if ("new".equals(notification.status)) {
String alarmText = notification.text;
switch (notification.type) {
case "alarm":
handler.alarmEvent(alarmText);
break;
case "notification":
handler.noticeEvent(alarmText);
break;
default:
logger.debug("Niko Home Control: unexpected message type {}", notification.type);
}
}
}
}
private void addDevice(NhcDevice2 device) {
String location = null;
if (device.parameters != null) {
location = device.parameters.stream().map(p -> p.locationName).filter(Objects::nonNull).findFirst()
.orElse(null);
}
if ("action".equals(device.type)) {
if (!actions.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding action device {}, {}", device.uuid, device.name);
ActionType actionType;
switch (device.model) {
case "generic":
case "pir":
case "simulation":
case "comfort":
case "alarms":
case "alloff":
case "overallcomfort":
case "garagedoor":
actionType = ActionType.TRIGGER;
break;
case "light":
case "socket":
case "switched-generic":
case "switched-fan":
actionType = ActionType.RELAY;
break;
case "dimmer":
actionType = ActionType.DIMMER;
break;
case "rolldownshutter":
case "sunblind":
case "venetianblind":
case "gate":
actionType = ActionType.ROLLERSHUTTER;
break;
default:
actionType = ActionType.GENERIC;
logger.debug("Niko Home Control: device type {} not recognised, default to GENERIC action",
device.type);
}
NhcAction2 nhcAction = new NhcAction2(device.uuid, device.name, device.model, device.technology,
actionType, location, this);
actions.put(device.uuid, nhcAction);
}
} else if ("thermostat".equals(device.type)) {
if (!thermostats.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding thermostat device {}, {}", device.uuid, device.name);
NhcThermostat2 nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.model,
device.technology, location, this);
thermostats.put(device.uuid, nhcThermostat);
}
} else if ("centralmeter".equals(device.type)) {
if (!energyMeters.containsKey(device.uuid)) {
logger.debug("Niko Home Control: adding centralmeter device {}, {}", device.uuid, device.name);
NhcEnergyMeter2 nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.model,
device.technology, this, scheduler);
energyMeters.put(device.uuid, nhcEnergyMeter);
}
} else {
logger.debug("Niko Home Control: device type {} not supported for {}, {}", device.type, device.uuid,
device.name);
}
}
private void removeDevice(NhcDevice2 device) {
if (actions.containsKey(device.uuid)) {
actions.get(device.uuid).actionRemoved();
actions.remove(device.uuid);
} else if (thermostats.containsKey(device.uuid)) {
thermostats.get(device.uuid).thermostatRemoved();
thermostats.remove(device.uuid);
} else if (energyMeters.containsKey(device.uuid)) {
energyMeters.get(device.uuid).energyMeterRemoved();
energyMeters.remove(device.uuid);
}
}
private void updateState(NhcDevice2 device) {
List<NhcProperty> deviceProperties = device.properties;
if (deviceProperties == null) {
logger.debug("Cannot Update state for {} as no properties defined in device message", device.uuid);
return;
}
if (actions.containsKey(device.uuid)) {
updateActionState((NhcAction2) actions.get(device.uuid), deviceProperties);
} else if (thermostats.containsKey(device.uuid)) {
updateThermostatState((NhcThermostat2) thermostats.get(device.uuid), deviceProperties);
} else if (energyMeters.containsKey(device.uuid)) {
updateEnergyMeterState((NhcEnergyMeter2) energyMeters.get(device.uuid), deviceProperties);
}
}
private void updateActionState(NhcAction2 action, List<NhcProperty> deviceProperties) {
if (action.getType() == ActionType.ROLLERSHUTTER) {
updateRollershutterState(action, deviceProperties);
} else {
updateLightState(action, deviceProperties);
}
}
private void updateLightState(NhcAction2 action, List<NhcProperty> deviceProperties) {
Optional<NhcProperty> statusProperty = deviceProperties.stream().filter(p -> (p.status != null)).findFirst();
Optional<NhcProperty> dimmerProperty = deviceProperties.stream().filter(p -> (p.brightness != null))
.findFirst();
Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
.findFirst();
String booleanState = null;
if (statusProperty.isPresent()) {
booleanState = statusProperty.get().status;
} else if (basicStateProperty.isPresent()) {
booleanState = basicStateProperty.get().basicState;
}
if (booleanState != null) {
if (NHCON.equals(booleanState)) {
action.setBooleanState(true);
logger.debug("Niko Home Control: setting action {} internally to ON", action.getId());
} else if (NHCOFF.equals(booleanState)) {
action.setBooleanState(false);
logger.debug("Niko Home Control: setting action {} internally to OFF", action.getId());
}
}
if (dimmerProperty.isPresent()) {
action.setState(Integer.parseInt(dimmerProperty.get().brightness));
logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(),
dimmerProperty.get().brightness);
}
}
private void updateRollershutterState(NhcAction2 action, List<NhcProperty> deviceProperties) {
deviceProperties.stream().map(p -> p.position).filter(Objects::nonNull).findFirst().ifPresent(position -> {
try {
action.setState(Integer.parseInt(position));
logger.debug("Niko Home Control: setting action {} internally to {}", action.getId(), position);
} catch (NumberFormatException e) {
logger.trace("Niko Home Control: received empty rollershutter {} position info", action.getId());
}
});
}
private void updateThermostatState(NhcThermostat2 thermostat, List<NhcProperty> deviceProperties) {
Optional<Boolean> overruleActiveProperty = deviceProperties.stream().map(p -> p.overruleActive)
.filter(Objects::nonNull).map(t -> Boolean.parseBoolean(t)).findFirst();
Optional<Integer> overruleSetpointProperty = deviceProperties.stream().map(p -> p.overruleSetpoint)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<Integer> overruleTimeProperty = deviceProperties.stream().map(p -> p.overruleTime)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t))).findFirst();
Optional<Integer> setpointTemperatureProperty = deviceProperties.stream().map(p -> p.setpointTemperature)
.filter(s -> !((s == null) || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<Boolean> ecoSaveProperty = deviceProperties.stream().map(p -> p.ecoSave).filter(Objects::nonNull)
.map(t -> Boolean.parseBoolean(t)).findFirst();
Optional<Integer> ambientTemperatureProperty = deviceProperties.stream().map(p -> p.ambientTemperature)
.filter(s -> !(s == null || s.isEmpty())).map(t -> Math.round(Float.parseFloat(t) * 10)).findFirst();
Optional<@Nullable String> demandProperty = deviceProperties.stream().map(p -> p.demand)
.filter(Objects::nonNull).findFirst();
Optional<@Nullable String> operationModeProperty = deviceProperties.stream().map(p -> p.operationMode)
.filter(Objects::nonNull).findFirst();
String modeString = deviceProperties.stream().map(p -> p.program).filter(Objects::nonNull).findFirst()
.orElse("");
int mode = IntStream.range(0, THERMOSTATMODES.length).filter(i -> THERMOSTATMODES[i].equals(modeString))
.findFirst().orElse(thermostat.getMode());
int measured = ambientTemperatureProperty.orElse(thermostat.getMeasured());
int setpoint = setpointTemperatureProperty.orElse(thermostat.getSetpoint());
int overrule = thermostat.getOverrule();
int overruletime = thermostat.getRemainingOverruletime();
if (overruleActiveProperty.orElse(false)) {
overrule = overruleSetpointProperty.orElse(0);
overruletime = overruleTimeProperty.orElse(0);
}
int ecosave = thermostat.getEcosave();
if (ecoSaveProperty.orElse(false)) {
ecosave = 1;
}
int demand = thermostat.getDemand();
String demandString = demandProperty.orElse(operationModeProperty.orElse(""));
demandString = demandString == null ? "" : demandString;
switch (demandString) {
case "None":
demand = 0;
break;
case "Heating":
demand = 1;
break;
case "Cooling":
demand = -1;
break;
}
logger.debug(
"Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
thermostat.getId(), measured, setpoint, mode, overrule, overruletime, ecosave, demand);
thermostat.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
deviceProperties.stream().map(p -> p.electricalPower).filter(Objects::nonNull).findFirst()
.ifPresent(electricalPower -> {
try {
energyMeter.setPower(Integer.parseInt(electricalPower));
logger.trace("Niko Home Control: setting energy meter {} power to {}", energyMeter.getId(),
electricalPower);
} catch (NumberFormatException e) {
energyMeter.setPower(null);
logger.trace("Niko Home Control: received empty energy meter {} power reading",
energyMeter.getId());
}
});
}
@Override
public void executeAction(String actionId, String value) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = actionId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty property = new NhcProperty();
deviceProperties.add(property);
device.properties = deviceProperties;
NhcAction2 action = (NhcAction2) actions.get(actionId);
switch (action.getType()) {
case GENERIC:
case TRIGGER:
property.basicState = NHCTRIGGERED;
break;
case RELAY:
property.status = value;
break;
case DIMMER:
if (NHCON.equals(value)) {
action.setBooleanState(true); // this will trigger sending the stored brightness value event out
property.status = value;
} else if (NHCOFF.equals(value)) {
property.status = value;
} else {
// If the light is off, turn the light on before sending the brightness value, needs to happen
// in 2 separate messages.
if (!action.booleanState()) {
executeAction(actionId, NHCON);
}
property.brightness = value;
}
break;
case ROLLERSHUTTER:
if (NHCSTOP.equals(value)) {
property.action = value;
} else if (NHCUP.equals(value)) {
property.position = "100";
} else if (NHCDOWN.equals(value)) {
property.position = "0";
} else {
int position = 100 - Integer.parseInt(value);
property.position = String.valueOf(position);
}
break;
}
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeThermostat(String thermostatId, String mode) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = thermostatId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty overruleActiveProp = new NhcProperty();
deviceProperties.add(overruleActiveProp);
overruleActiveProp.overruleActive = "False";
NhcProperty program = new NhcProperty();
deviceProperties.add(program);
program.program = mode;
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeThermostat(String thermostatId, int overruleTemp, int overruleTime) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = thermostatId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
if (overruleTime > 0) {
NhcProperty overruleActiveProp = new NhcProperty();
overruleActiveProp.overruleActive = "True";
deviceProperties.add(overruleActiveProp);
NhcProperty overruleSetpointProp = new NhcProperty();
overruleSetpointProp.overruleSetpoint = String.valueOf(overruleTemp / 10.0);
deviceProperties.add(overruleSetpointProp);
NhcProperty overruleTimeProp = new NhcProperty();
overruleTimeProp.overruleTime = String.valueOf(overruleTime);
deviceProperties.add(overruleTimeProp);
} else {
NhcProperty overruleActiveProp = new NhcProperty();
overruleActiveProp.overruleActive = "False";
deviceProperties.add(overruleActiveProp);
}
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void startEnergyMeter(String energyMeterId) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
ArrayList<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
ArrayList<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = energyMeterId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty reportInstantUsageProp = new NhcProperty();
deviceProperties.add(reportInstantUsageProp);
reportInstantUsageProp.reportInstantUsage = "True";
device.properties = deviceProperties;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
((NhcEnergyMeter2) energyMeters.get(energyMeterId)).startEnergyMeter(topic, gsonMessage);
}
@Override
public void stopEnergyMeter(String energyMeterId) {
((NhcEnergyMeter2) energyMeters.get(energyMeterId)).stopEnergyMeter();
}
/**
* Method called from the {@link NhcEnergyMeter2} object to send message to Niko Home Control.
*
* @param topic
* @param gsonMessage
*/
public void executeEnergyMeter(String topic, String gsonMessage) {
sendDeviceMessage(topic, gsonMessage);
}
private void sendDeviceMessage(String topic, String gsonMessage) {
try {
mqttConnection.connectionPublish(topic, gsonMessage);
} catch (MqttException e) {
logger.warn("Niko Home Control: sending command failed, trying to restart communication");
restartCommunication();
// retry sending after restart
try {
if (communicationActive()) {
mqttConnection.connectionPublish(topic, gsonMessage);
} else {
logger.warn("Niko Home Control: failed to restart communication");
connectionLost();
}
} catch (MqttException e1) {
logger.warn("Niko Home Control: error resending device command");
connectionLost();
}
}
}
@Override
public void processMessage(String topic, byte[] payload) {
String message = new String(payload);
if ((profile + "/system/evt").equals(topic)) {
systemEvt(message);
} else if ((profile + "/system/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
systeminfoPublishRsp(message);
} else if ((profile + "/notification/evt").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
notificationEvt(message);
} else if ((profile + "/control/devices/evt").equals(topic)) {
logger.trace("Niko Home Control: received topic {}, payload {}", topic, message);
devicesEvt(message);
} else if ((profile + "/control/devices/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
devicesListRsp(message);
} else if ((profile + "/authentication/rsp").equals(topic)) {
logger.debug("Niko Home Control: received topic {}, payload {}", topic, message);
servicesListRsp(message);
} else if ((profile + "/control/devices.error").equals(topic)) {
logger.warn("Niko Home Control: received error {}", message);
} else {
logger.trace("Niko Home Control: not acted on received message topic {}, payload {}", topic, message);
}
}
/**
* @return system info retrieved from Connected Controller
*/
public NhcSystemInfo2 getSystemInfo() {
NhcSystemInfo2 systemInfo = nhcSystemInfo;
if (systemInfo == null) {
systemInfo = new NhcSystemInfo2();
}
return systemInfo;
}
/**
* @return time info retrieved from Connected Controller
*/
public NhcTimeInfo2 getTimeInfo() {
NhcTimeInfo2 timeInfo = nhcTimeInfo;
if (timeInfo == null) {
timeInfo = new NhcTimeInfo2();
}
return timeInfo;
}
/**
* @return comma separated list of services retrieved from Connected Controller
*/
public String getServices() {
return services.stream().map(NhcService2::name).collect(Collectors.joining(", "));
}
@Override
public void connectionStateChanged(MqttConnectionState state, @Nullable Throwable error) {
if (error != null) {
logger.debug("Connection state: {}", state, error);
restartCommunication();
if (!communicationActive()) {
logger.warn("Niko Home Control: failed to restart communication");
connectionLost();
}
} else {
logger.trace("Connection state: {}", state);
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="nikohomecontrol" 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>Niko Home Control Binding</name>
<description>This is the binding for the Niko Home Control system</description>
<author>Mark Herwege</author>
</binding:binding>

View File

@@ -0,0 +1,281 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="nikohomecontrol"
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>Niko Home Control I Bridge</label>
<description>This bridge represents a Niko Home Control I IP-interface</description>
<channels>
<channel id="alarm" typeId="alarm"/>
<channel id="notice" typeId="notice"/>
</channels>
<config-description>
<parameter name="addr" type="text" required="true">
<label>IP or Host Name</label>
<description>IP Address of Niko Home Control IP-interface</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
<label>Bridge Port</label>
<description>Port to communicate with Niko Home Control IP-interface, default 8000</description>
<default>8000</default>
<advanced>true</advanced>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval for connection with Niko Home Control IP-interface (min), default 300. If set to 0 or
left empty, no refresh will be scheduled</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<bridge-type id="bridge2">
<label>Niko Home Control II Bridge</label>
<description>This bridge represents a Niko Home Control II Connected Controller</description>
<channels>
<channel id="alarm" typeId="alarm"/>
<channel id="notice" typeId="notice"/>
</channels>
<config-description>
<parameter name="addr" type="text" required="true">
<label>IP or Host Name</label>
<description>IP Address of Connected Controller</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
<label>Bridge Port</label>
<description>Port for secure MQTT communication with Connected Controller, default 8884</description>
<default>8884</default>
<advanced>true</advanced>
</parameter>
<parameter name="profile" type="text">
<label>Profile</label>
<description>Profile used in Niko Home Control II for hobby API</description>
<default>hobby</default>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text" required="true">
<label>API Token</label>
<description>Token for Niko Home Control II hobby API, should not be empty. This token will have to be renewed after
expiration (1 year after creation)</description>
<context>password</context>
</parameter>
<parameter name="refresh" type="integer">
<label>Refresh Interval</label>
<description>Refresh interval for connection with Connected Controller (min), default 300. If set to 0 or left
empty, no refresh will be scheduled</description>
<default>300</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<thing-type id="pushButton">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Pushbutton</label>
<description>Pushbutton type action in Niko Home Control</description>
<channels>
<channel id="button" typeId="button"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="onOff">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Switch</label>
<description>On/Off type action in Niko Home Control</description>
<channels>
<channel id="switch" typeId="system.power"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="dimmer">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Dimmer</label>
<description>Dimmer type action in Niko Home Control</description>
<channels>
<channel id="brightness" typeId="system.brightness"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
<parameter name="step" type="integer" required="true">
<label>Step Value</label>
<description>Step value used for increase/decrease of dimmer brightness, default 10%</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="blind">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Shutter</label>
<description>Rollershutter type action in Niko Home Control</description>
<channels>
<channel id="rollershutter" typeId="rollershutter"/>
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>Action ID</label>
<description>Niko Home Control action ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="thermostat">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Thermostat</label>
<description>Thermostat in the Niko Home Control system</description>
<channels>
<channel id="measured" typeId="measured"/>
<channel id="mode" typeId="mode"/>
<channel id="setpoint" typeId="setpoint"/>
<channel id="overruletime" typeId="overruletime"/>
</channels>
<config-description>
<parameter name="thermostatId" type="text" required="true">
<label>Thermostat ID</label>
<description>Niko Home Control Thermostat ID</description>
<advanced>false</advanced>
</parameter>
<parameter name="overruleTime" type="integer">
<label>Overrule Time</label>
<description>Default overrule duration in minutes when an overrule temperature is set without providing overrule
time, 60 minutes by default</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="energyMeter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>Energy Meter</label>
<description>Energy meter in the Niko Home Control system</description>
<channels>
<channel id="power" typeId="power"/>
</channels>
<config-description>
<parameter name="energyMeterId" type="text" required="true">
<label>Energy Meter ID</label>
<description>Niko Home Control Energy Meter ID</description>
<advanced>false</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="button">
<item-type>Switch</item-type>
<label>Button</label>
<description>Pushbutton control for action in Niko Home Control</description>
<category>Switch</category>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="rollershutter">
<item-type>Rollershutter</item-type>
<label>Rollershutter</label>
<description>Rollershutter control for rollershutter action in Niko Home Control</description>
<category>Blinds</category>
</channel-type>
<channel-type id="measured">
<item-type>Number:Temperature</item-type>
<label>Measured</label>
<description>Temperature measured by thermostat</description>
<category>Temperature</category>
<tags>
<tag>CurrentTemperature</tag>
</tags>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="setpoint">
<item-type>Number:Temperature</item-type>
<label>Setpoint</label>
<description>Setpoint temperature of thermostat</description>
<category>Temperature</category>
<tags>
<tag>TargetTemperature</tag>
</tags>
<state min="0" max="100" step="0.5" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="overruletime">
<item-type>Number</item-type>
<label>Overrule Time</label>
<description>Time duration for overruling thermostat target temperature in min.</description>
<category>Number</category>
<state min="0" max="1440" step="5"/>
</channel-type>
<channel-type id="mode">
<item-type>Number</item-type>
<label>Mode</label>
<description>Thermostat mode</description>
<category>Number</category>
<state>
<options>
<option value="0">day</option>
<option value="1">night</option>
<option value="2">eco</option>
<option value="3">off</option>
<option value="4">cool</option>
<option value="5">prog 1</option>
<option value="6">prog 2</option>
<option value="7">prog 3</option>
</options>
</state>
</channel-type>
<channel-type id="power">
<item-type>Number:Power</item-type>
<label>Power</label>
<description>Momentary power consumption/production (positive is consumption)</description>
<category>Number</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="alarm">
<kind>trigger</kind>
<label>Alarm</label>
<description>Alarm from Niko Home Control</description>
</channel-type>
<channel-type id="notice">
<kind>trigger</kind>
<label>Notice</label>
<description>Notice from Niko Home Control</description>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,69 @@
newintermediate-certificate = -----BEGIN CERTIFICATE-----\r\n\
MIIF7jCCA9agAwIBAgICEA4wDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAkJF\
MRgwFgYDVQQIDA9Pb3N0LVZsYWFuZGVyZW4xFTATBgNVBAcMDFNpbnQtTmlrbGFh\
czENMAsGA1UECgwETmlrbzEVMBMGA1UECwwMSG9tZSBDb250cm9sMRswGQYJKoZI\
hvcNAQkBFgxpbmZvQG5pa28uYmUwHhcNNzAwMTAxMDAwMDAwWhcNMzcwMTAxMDAw\
MDAwWjCBiTELMAkGA1UEBhMCQkUxGDAWBgNVBAgMD09vc3QtVmxhYW5kZXJlbjEN\
MAsGA1UECgwETmlrbzEVMBMGA1UECwwMSG9tZSBDb250cm9sMR0wGwYDVQQDDBRO\
aWtvIEludGVybWVkaWF0ZSBDQTEbMBkGCSqGSIb3DQEJARYMaW5mb0BuaWtvLmJl\
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuSDk7ob45c78+b/SSfMl\
TOY82nJ/RQjNrIFRTdMUwrt18GMz2TDXJnaz+N5bkxC4L2CkPWZE3eOr3l10al+r\
hZ+m55AhZZcoHHN9vFIul8pw86mVAY8uxr3pM72/270L9yJ+Ra8d+qwM6+L8zWUc\
S/RoGokyutkzfuC20tC1u8IOsUgNHuHwh2dWA0OrI+GWZ6k+Mr/Ojsj7YL5xIrOK\
eZHIN0jy6/hSnWDN1GTxIKpiKCOoFUGAj5Wwpf3Z3mpmSIvAG048fczX2ZdcjCcg\
Iaiw5yeK77G5iMYtzPxJwZRKBVfo+Kf0sPn7QSOJwMJZ8KRgO1KAysuCtspUsemg\
mA0I0pzXOwFJI5dIquMj/2vO+JFB+T8XeoPdeaOc9RJA5Wj2ENIjHTu/W86ElJwU\
8Aw3Z6Gc63mto4FGkM7kN7VQyQVX7EbTmuMC5gHDltrYpsnlKz2d0pShBg++x6IY\
Hd321i8HGqg7NyfG6jZpISQSKKzPZKG++9l2/w7eQ8qJYpGZ6zqiUphygKdx9q2s\
sP8AUbKYZzRBK0u4XDwtJtYAaNw5arKGH4qLHn+EEYTruC1fo9SAGqkPoACd0Oze\
3w8tjsHwwzD8NXJzEpnUyjDmtvi1VfUzKlc82CrNW6iePzR0lGzEQtVBI4rfqbfJ\
RvQ9Hq9HaCrX1P6M5s/ZfisCAwEAAaNmMGQwHQYDVR0OBBYEFHoJvtyYZ7/j4nDe\
kGT2q+xKCWE/MB8GA1UdIwQYMBaAFOa0NGf2t36uYioWVapmm073eJBZMBIGA1Ud\
EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IC\
AQBsl6Y9A5qhTMQHL+Z7S0+t1xjRuzYtzgddEGIWK9nsx1RmH6NDv4KoziaUX3Tx\
YMlOFqx6Q+P0Tyl+x+wbPC3stVa8h4hmr5mOd1ph15hecAK0VbcWeRfMAEqg9o47\
6MheSjXwwIp/oRx3YCX3MhiBaWj1IgLElOA99Xtlksk6VsR6QVoSmTKUNDR0T3TF\
AKq6AH+IIa4wsMlXdkK7LQFGnArmYwXuTyVpDoaYbYP9F5sXslfa294oqPp0kfUl\
niyzX0jLYKAL7CqEBzMXVtLPo2Be6X6uagBIz6MV8s1FGmETf++pWKsuvR9EOoh8\
Cm0xozW9WlPm0dBeMyT991QqDkfaMyOtFT6KZwkD3HxAiTBOZ1LI/P00kaPjpJwt\
+8OKGjqQcXBn6p4ZxF6AmZ9fMCWkYyG37HwSeQYJM/zqrbP+Opfl6dgGJ+Qa5P6k\
1f8YzBkE1gG1V9YcAAWOGPMOgqBE0V0uZfPVctp4wcC4WBqti4pYC28+iHdewQzl\
9LB6RwIJmWNrhRLY+fdutV8NgTVb44vtkaQ+ewyc8y01Fk/G0HXarPt3UYgO6oqa\
FpEU/wi2o9qMVgvHmkXdR1yQLSYZs2R/yzE1KDUSOmxa5T+XFfW7KQ07fhwk27Gk\
y7Ob3mU1LT25MO7yLXUjGqNj9k9aa5FLUTyoh1JGGM64Zw==\r\n\
-----END CERTIFICATE-----\r\n
newca-certificate = -----BEGIN CERTIFICATE-----\r\n\
MIIF6jCCA9KgAwIBAgIJANTA8rXGnhG7MA0GCSqGSIb3DQEBCwUAMIGBMQswCQYD\
VQQGEwJCRTEYMBYGA1UECAwPT29zdC1WbGFhbmRlcmVuMRUwEwYDVQQHDAxTaW50\
LU5pa2xhYXMxDTALBgNVBAoMBE5pa28xFTATBgNVBAsMDEhvbWUgQ29udHJvbDEb\
MBkGCSqGSIb3DQEJARYMaW5mb0BuaWtvLmJlMB4XDTcwMDEwMTAwMDAwNVoXDTM3\
MDEyOTAwMDAwNVowgYExCzAJBgNVBAYTAkJFMRgwFgYDVQQIDA9Pb3N0LVZsYWFu\
ZGVyZW4xFTATBgNVBAcMDFNpbnQtTmlrbGFhczENMAsGA1UECgwETmlrbzEVMBMG\
A1UECwwMSG9tZSBDb250cm9sMRswGQYJKoZIhvcNAQkBFgxpbmZvQG5pa28uYmUw\
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpKNKKHC0fCND19D96G78G\
Zdj+OGLvy/DRJswbLepG8cPedqEZwXjn762fvLdTlcTX/ohkeG4QPb1mxPzjpEgl\
M5aNmp2rmlAFVtLWILQx7mWir5FjG5eTyYi2fbYHnPQpx8XuVk2INENd85818R4j\
RfouYLaZWSd8wc7LP20N0rVtjg5RJ/zAkQ6A7KzdgeOkKhn07wSGBWu9vDw7gCdL\
+Oyeo4LQmABXB7up8nIDCl+o23QL4/aSzdrS5cBCXoPWwto7OiXw0RRcEbpumQyW\
mTGS8jT2FCUNAIWAxC3pKEIXbzf03pLo7EMfFcmjsLDcvcnkB+EJX0fuATwl5CLz\
SneUFY7MNTpv9xgZFX83LhoiFbycZwzWEUr/Q0pmHYZdmezm84+W6EA3E9qH+oR8\
V09bwEMAMSQpbebEB8JmvvwykQHxowkpnV01bmimBEOaquAmyfiW3YSO90vJu+kg\
Zrkihc0AEMFcDbLRCEKvx/u6Hs2xMmVPz0W9mPW37t5zKOV0vcrHmFgMp+9EyDAQ\
vfNofLx790lD1LFp3qvD/H0+IbydQoEc7Q1/tTQDjL45TLNXwwBWQVQLIEQY5sqN\
n8p2ita3MPpSnu5XU93pBcns8jUNlc6/wFIMSBDWK40RiJKzTsr/2jTGVqZX8PXA\
rDnIoa0Eapt0nq87qnkQzQIDAQABo2MwYTAdBgNVHQ4EFgQU5rQ0Z/a3fq5iKhZV\
qmabTvd4kFkwHwYDVR0jBBgwFoAU5rQ0Z/a3fq5iKhZVqmabTvd4kFkwDwYDVR0T\
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAFLw\
6lbxex6hElSrqOZljoFQzQg78dmtUFm4BgKu5EAqn1Ug/OHOKk8LBbNMf2X0Y+4i\
SO4g8yO/C9M/YPxjnM5As1ouTu7UzQL4hEJynyHoPuCBD8nZxuaKeGMX7nQYm7GD\
0iaL0iP9gFwv/A2O/isQUB/sTAclhm1zKAw4f/SaBq8t22wf59e8na0xIfHui0PD\
s8PfRbC4xIOKMxHkHFv+DHeMGjCbR4x20RV/z4JNx1ALEBGo6Oh7Dph/maAQWbje\
x9BCstNR3V1Bhx9rUe7BjIMyJUGEItpZXG+N+qnQr2K7xDdloJl4X0flIa74sdUE\
K4s0X7p+JixLMSxbu5oS6W+d3g6EG0ZgEUwwwc98D1fsm1ziNqwcnYMkI6P2601G\
kEaK/54kYqCxvw6fu5+PNmsDD8ptdazoO3/UOxWvspI1U3drcpnaEHuNclEF7WeL\
yqTfi+8UiL9xJgq9ivjKjZdchkdaD2THgrnzs0XxLbZnwAPeh3cHooUJQkInmKp3\
O05Gv0rnSr29bH8vh/sy4/yJJCUd036pF9C8mPHAYsvNDVGaGYVmNt5P28z3PO16\
YKNJCOJ0x333F6PJaqWAQQP9bGMuJThX8ZQ9Fd8KMXVUfFVKICEkb4erWpL2RIz3\
9JFSC56ZtXv2losfASTyXJwCpyib7FcTZ1rJze+l\r\n\
-----END CERTIFICATE-----\r\n