added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.nikohomecontrol/.classpath
Normal file
32
bundles/org.openhab.binding.nikohomecontrol/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.nikohomecontrol/.project
Normal file
23
bundles/org.openhab.binding.nikohomecontrol/.project
Normal 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>
|
||||
13
bundles/org.openhab.binding.nikohomecontrol/NOTICE
Normal file
13
bundles/org.openhab.binding.nikohomecontrol/NOTICE
Normal file
@@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
348
bundles/org.openhab.binding.nikohomecontrol/README.md
Normal file
348
bundles/org.openhab.binding.nikohomecontrol/README.md
Normal 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
|
||||
```
|
||||
17
bundles/org.openhab.binding.nikohomecontrol/pom.xml
Normal file
17
bundles/org.openhab.binding.nikohomecontrol/pom.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<String, {@link NhcAction}></code>
|
||||
*/
|
||||
public Map<String, NhcAction> getActions() {
|
||||
return actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all thermostats in the Niko Home Control Controller.
|
||||
*
|
||||
* @return <code>Map<String, {@link NhcThermostat}></code>
|
||||
*/
|
||||
public Map<String, NhcThermostat> getThermostats() {
|
||||
return thermostats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all energyMeters meters in the Niko Home Control Controller.
|
||||
*
|
||||
* @return <code>Map<String, {@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) {
|
||||
};
|
||||
}
|
||||
@@ -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" };
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 = "";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user