[nikohomecontrol] Energy meters and access control (#12893)

* NHCI energy meters and NHCII access control

Signed-off-by: Mark Herwege <mark.herwege@telenet.be>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Mark Herwege 2024-05-24 17:43:01 +02:00 committed by Ciprian Pascu
parent b34f66c148
commit 6c858df407
45 changed files with 3802 additions and 1303 deletions

View File

@ -1,6 +1,6 @@
# Niko Home Control Binding
**Upgrade notice for Niko Home Control II and openHAB 2.5.3**:
**Upgrade notice for Niko Home Control II and openHAB 2.5.3 or later**:
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.
@ -9,7 +9,7 @@ Request a hobby API token at [mynikohomecontrol](https://mynikohomecontrol.niko.
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 **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.
@ -24,13 +24,29 @@ The installation only needs to be 'connected' (registered on the Niko Home Contr
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).
Supported device types are switches, dimmers and rollershutters or blinds, thermostats, energy meters (Niko Home Control I only) and access control (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).
Connected to a bridge, the Niko Home Control Binding supports all off actions, on/off actions (e.g. for lights or groups of lights), dimmers, rollershutters or blinds, thermostats, energy meters (Niko Home Control I only) and access control devices (Niko Home Control II only).
The following thing types are available in the binding:
| Thing Type | NHC I | NHC II | Description |
|---------------------|:-----:|:------:|-----------------------------------------------------------------------------------|
| pushButton | x | x | maps directly to stateless actions in the system, such as all off actions |
| onOff | x | x | on/off type of action with on/off state, such as lights and sockets |
| dimmer | x | x | dimmable light action |
| blind | x | x | rollershutter, venetian blind |
| thermostat | x | x | thermostat |
| energyMeterLive | x | x | energy meter with live power monitoring and aggregation |
| energyMeter | x | | energy meter, aggregates readings with 10 min intervals |
| gasMeter | x | | gas meter, aggregates readings with 10 min intervals |
| waterMeter | x | | water meter, aggregates readings with 10 min intervals |
| access | | x | door with bell button and lock |
| accessRingAndComeIn | | x | door with bell button, lock and ring and come in functionality |
## Binding Configuration
@ -45,7 +61,7 @@ If the IP-address is set on a manually created bridge, no attempt will be made t
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 II, the port is set to the default Hobby API 8884 port.
For Niko Home Control I, no further bridge configuration is required when using auto-discovery.
@ -62,93 +78,69 @@ 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.
A discovery scan will first discover Niko Home Control IP-interfaces or Niko Home Control Connected Controllers in the network as bridges.
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.
Note that this may fail to find the Niko Home Control IP-interface when traffic to port 10000 on the openHAB server is blocked.
When the Niko Home Control bridge is added as a thing, the system information will be read from the Niko Home Control Controller and will update the bridge properties.
When a Niko Home Control bridge is added as a bridge, the system information will be read from the Niko Home Control Controller and will update the bridge properties.
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.
Note that Niko Home Control II will require the API 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.
Locations can subsequently be changed through the thing location parameter.
## Thing Configuration
## Bridge Configuration
If you wish to use textual config instead of automatic discovery, you can add thing definitions to a things file.
There are two **bridge** types, `bridge` for Niko Home Control I and `bridge2` for Niko Home Control II.
The Thing configuration for the bridge has the following parameters:
The Thing configuration for the **bridge** uses the following syntax:
| Parameter | NHC I | NHC II | Required | Description |
|-----------|:-----:|:------:|:--------:|--------------------------------------------------------------------------------------------------------|
| addr | x | x | x | IP address of the Niko Home Control IP-interface, Connected Controller or Wireless Smart Hub |
| port | x | x | | port used to connect, 8000 by default for NHC I, 8884 by default for NHC II |
| profile | | x | | profile UUID being used, hobby by default |
| password | | x | x | API token retrieved from the Niko Home Control website, must be set for the bridge to go online |
| refresh | x | x | | 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 I:
For Niko Home Control I, an example manual configuration looks like:
```java
Bridge nikohomecontrol:bridge:<bridgeId> [ addr="<IP-address of IP-interface>", port=<listening port>,
refresh=<Refresh interval> ]
Bridge nikohomecontrol:bridge:controller [ addr="192.168.0.10", refresh=0 ]
```
`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:
For Niko Home Control II, it looks like:
```java
Bridge nikohomecontrol:bridge2:<bridgeId> [ addr="<IP-address of IP-interface>", port=<listening port>, profile="<profile>",
password="<token>", refresh=<Refresh interval> ]
Bridge nikohomecontrol:bridge2:controller [ addr="192.168.0.10", password="<token>", refresh=0 ]
```
`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.
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.
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 `password` parameter should be set to the profile password.
The Thing configuration for **Niko Home Control actions** has the following syntax:
## Thing Configuration
```java
Thing nikohomecontrol:<thing type>:<bridgeId>:<thingId> "Label" @ "Location"
[ actionId="<Niko Home Control action ID>",
step=<dimmer increase/decrease step value> ]
```
The Thing configurations for **Niko Home Control actions, thermostats, energy meters and access devices** have the following parameters:
or nested in the bridge configuration:
| Parameter | NHC I | NHC II | Required | Thing Types | Description |
|---------------|:-----:|:------:|:--------:|----------------------------------|-----------------------------------------------------------------------------------|
| actionId | x | x | x | pushButton, onOff, dimmer, blind | unique ID for the action in the controller |
| step | x | x | | dimmer | step value for dimmer increase/decrease actions, 10 by default |
| invert | x | x | | blind, energyMeterLive, energyMeter, gasMeter, waterMeter | inverts rollershutter or blind direction. Inverts sign of meter reading. Default false |
| thermostatId | x | x | x | thermostat | unique ID for the thermostat in the controller |
| overruleTime | x | x | | thermostat | standard overrule duration in minutes when setting a new setpoint without providing an overrule duration, default value is 60 |
| meterId | x | x | x | energyMeterLive, energyMeter, gasMeter, waterMeter | unique ID for the energy meter in the controller |
| refresh | x | x | | energyMeterLive, energyMeter, gasMeter, waterMeter | refresh interval for meter reading in minutes, default 10 minutes. The value should not be lower than 5 minutes to avoid too many meter data retrieval calls |
| accessId | | x | x | access, accessRingAndComeIn | unique ID for the access device in the controller |
```java
<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:
```text
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.
For Niko Home Control I, the `actionId`, `thermostatId` or `meterId` parameter are 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.
@ -161,106 +153,59 @@ Note down the `actionId` parameter from the thing, remove it before adding it ag
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 same applies applies for `thermostatId`, `meterId` and `accessId`.
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:
An example **action** textual configuration looks like:
```java
Thing nikohomecontrol:thermostat:<bridgeId>:<thingId> "Label" @ "Location"
[ thermostatId="<Niko Home Control thermostat ID>",
overruleTime=<default duration for overrule temperature in minutes> ]
Thing nikohomecontrol:dimmer:mybridge:mydimmer [ actionId="1", step=5 ]
```
or nested in the bridge configuration:
For **thermostats** it would be:
```java
thermostat <thingId> "Label" @ "Location" [ thermostatId="<Niko Home Control thermostat ID>" ]
Thing nikohomecontrol:thermostat:mybridge:mythermostat [ thermostatId="abcdef01-abcd-1234-ab98-012345abcdef", overruleTime=10 ]
```
`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:
For **energy meters**:
```java
Thing nikohomecontrol:energymeter:<bridgeId>:<thingId> "Label" @ "Location"
[ energyMeterId="<Niko Home Control energy meter ID>" ]
Thing nikohomecontrol:energymeter:mybridge:mymeter [ meterId="3", refresh=30 ]
```
or nested in the bridge configuration:
For **access devices**:
```java
energymeter <thingId> "Label" @ "Location" [ energyMeterId="<Niko Home Control energy meter ID>" ]
Thing nikohomecontrol:accessRingAndComeIn:mybridge:myaccess [ accessId="abcdef01-dcba-1234-ab98-012345abcdef" ]
```
`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`, `heatingmode`, `mode`, `setpoint`, `overruletime`, `heatingdemand` and `demand`.
`measured` gives the current temperature in QuantityType<Temperature>, allowing for different temperature units.
This channel is read only.
`heatingmode` can be set and shows the current thermostat mode.
Allowed values are Day, Night, Eco, Off, Cool, Prog1, Prog2, Prog3.
As an alternative to `heatingmode` and for backward compatibility, the advanced channel `mode` is provided.
This channel has the same meaning, but with numeric values (0=Day, 1=Night, 2=Eco, 3=Off, 4=Cool, 5=Prog1, 6=Prog2, 7=Prog3) instead of string values.
If `heatingmode` or `mode` is set, the `setpoint` temperature will return to the standard value for the mode as defined in Niko Home Control.
`setpoint` 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 from its mode.
`heatingdemand` is a string indicating if the system is actively heating/cooling.
This channel will have value Heating, Cooling or None.
As an alternative to `heatingdemand`, the advanced channel `demand` is provided.
The value will be 1 for heating, -1 for cooling and 0 if not heating or cooling.
`heatingdemand` and `demand` are read only channels.
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.
| Channel Type ID | RW | Advanced | Item Type | Thing Types | Description |
|-----------------|:--:|:--------:|--------------------|-------------|-----------------------------------------------------------------------------------------------------|
| button | RW | | Switch | pushButton | stateless action control, `autoupdate="false"` by default. Only accepts `ON` |
| switch | RW | | Switch | onOff | on/off switches with state control, such as light switches |
| brightness | RW | | Dimmer | dimmer | control dimmer light intensity. 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 |
| rollershutter | RW | | Rollershutter | blind | control rollershutter or blind. UpDown, StopMove and Percent command types are supported |
| measured | R | | Number:Temperature | thermostat | current temperature. Because of API restrictions, NHC II will only report in 0.5°C increments |
| heatingmode | RW | | String | thermostat | current thermostat mode. Allowed values are Day, Night, Eco, Off, Cool, Prog1, Prog2, Prog3. Setting `heatingmode` will reset the `setpoint` channel to the standard value for the mode in the controller |
| mode | RW | X | Number | thermostat | current thermostat mode, same meaning as `heatingmode`, but numeric values (0=Day, 1=Night, 2=Eco, 3=Off, 4=Cool, 5=Prog1, 6=Prog2, 7=Prog3). Setting `mode` will reset the `setpoint` channel to the standard value for the mode in the controller. This channel is kept for binding backward compatibility |
| setpoint | RW | | Number:Temperature | thermostat | current thermostat setpoint. Updating the `setpoint` will overrule the temperature setpoint defined by `heatingmode` or `mode` for `overruletime` duration. Because of API restrictions, NHC II will only report in 0.5°C increments |
| overruletime | RW | | Number | thermostat | used to set the total duration in minutes to apply the setpoint temperature set in the `setpoint` channel before the thermostat returns to the setting from its mode |
| heatingdemand | R | | String | thermostat | indicating if the system is actively heating/cooling. This channel will have value Heating, Cooling or None. For NHC I this is set by the binding from the temperature difference between `setpoint` and `measured`. It therefore may incorrectly indicate cooling even when the system does not have active cooling capabilities |
| demand | R | X | Number | thermostat | indicating if the system is actively heating/cooling, same as `heatingdemand` but numeric values (-1=Cooling, 0=None, 1=Heating) |
| power | R | | Number:Power | energyMeterLive | instant power consumption/production (negative for production), refreshed every 2s. Linking this channel starts an intensive communication flow with the controller and should only be done when appropriate |
| energy | R | | Number:Energy | energyMeterLive, energyMeter | total energy meter reading |
| energyday | R | | Number:Energy | energyMeterLive, energyMeter | day energy meter reading |
| gas | R | | Number:Volume | gasMeter | total gas meter reading |
| gasday | R | | Number:Volume | gasMeter | day gas meter reading |
| water | R | | Number:Volume | waterMeter | total water meter reading |
| waterday | R | | Number:Volume | waterMeter | day water meter reading |
| measurementtime | R | | DateTimeType | energyMeterLive, energyMeter, gasMeter, waterMeter | last meter reading time |
| bellbutton | RW | | Switch | access, accessRingAndComeIn | bell button connected to access device, including buttons on video phone devices linked to an access device. The bell can also be triggered by an `ON` command, `autoupdate="false"` by default |
| ringandcomein | RW | | Switch | accessRingAndComeIn | provide state and turn automatic door unlocking at bell ring on/off |
| lock | RW | | Switch | access, accessRingAndComeIn | provide doorlock state and unlock the door by sending an `OFF` command. `autoupdate="false"` by default |
The bridge has two trigger channels `alarm` and `notice`.
It can be used as a trigger to rules.
@ -270,14 +215,8 @@ The event message is the alarm or notice text coming from Niko Home Control.
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.
Not all action and device types supported in Niko Home Control I and II controllers are supported by the binding.
Refer to the list of things and their support for Niko Home Control I and II respectively.
## Example
@ -290,6 +229,8 @@ Bridge nikohomecontrol:bridge:nhc1 [ addr="192.168.0.70", port=8000, refresh=300
dimmer 3 "TVRoom" [ actionId="3", step=5 ]
blind 4 [ actionId="4" ]
thermostat 5 [ thermostatId="0", overruleTime=10 ]
energyMeterLive 6 [ meterId="1" ]
waterMeter [ meterId="3" ]
}
Bridge nikohomecontrol:bridge2:nhc2 [ addr="192.168.0.70", port=8884, password="A.B.C", refresh=300 ] {
@ -298,7 +239,7 @@ Bridge nikohomecontrol:bridge2:nhc2 [ addr="192.168.0.70", port=8884, password="
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 ]
energymeter 6 [ energyMeterId="abcdef01-abcd-1234-cd56-ffee34567890" ]
accessRingAndComeIn 6 [ accessId="abcdef01-abcd-1234-ab98-012345abcdef" ]
}
Bridge nikohomecontrol:bridge:nhc3 [ addr="192.168.0.110" ] {
@ -321,7 +262,9 @@ String ThermostatMode {channel="nikohomecontrol:thermostat:nhc1:5:heatingmode"
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
String ThermostatDemand {channel="nikohomecontrol:thermostat:nhc1:5:heatingdemand"} # Get the current heating/cooling demand
Number:Power CurPower "[%.0f W]" {channel="nikohomecontrol:energyMeter:nhc2:6:power"} # Get current power consumption
Number:Power CurPower "[%.0f W]" {channel="nikohomecontrol:energyMeterLive:nhc1:6:power"} # Get current power consumption
Number:Energy MyMeter "[%.0f kWh]" {channel="nikohomecontrol:energyMeter:nhc1:7:energy # Get energy meter reading
Number:Energy MyMeterDay "[%.0f kWh]" {channel="nikohomecontrol:energyMeter:nhc1:7:energyday # Get energy meter day reading
```
.sitemap:
@ -336,7 +279,9 @@ Text item=CurTemperature
Selection item=ThermostatMode mappings=[Day="day", Night="night", Eco="eco", Off="off", Prog1="Away"]
Setpoint item=SetTemperature minValue=0 maxValue=30
Slider item=OverruleDuration minValue=0 maxValue=120
Text item=Power
Text item=CurPower
Text item=MyMeter
Text item=MyMeterDay
```
Example trigger rule:

View File

@ -12,7 +12,6 @@
*/
package org.openhab.binding.nikohomecontrol.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -46,17 +45,26 @@ public class NikoHomeControlBindingConstants {
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_LIVE = new ThingTypeUID(BINDING_ID, "energyMeterLive");
public static final ThingTypeUID THING_TYPE_ENERGYMETER = new ThingTypeUID(BINDING_ID, "energyMeter");
public static final ThingTypeUID THING_TYPE_GASMETER = new ThingTypeUID(BINDING_ID, "gasMeter");
public static final ThingTypeUID THING_TYPE_WATERMETER = new ThingTypeUID(BINDING_ID, "waterMeter");
public static final ThingTypeUID THING_TYPE_ACCESS = new ThingTypeUID(BINDING_ID, "access");
public static final ThingTypeUID THING_TYPE_ACCESS_RINGANDCOMEIN = new ThingTypeUID(BINDING_ID,
"accessRingAndComeIn");
// 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()));
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Set.of(BRIDGEI_THING_TYPE, BRIDGEII_THING_TYPE);
public static final Set<ThingTypeUID> ACTION_THING_TYPES_UIDS = Set.of(THING_TYPE_PUSHBUTTON,
THING_TYPE_ON_OFF_LIGHT, THING_TYPE_DIMMABLE_LIGHT, THING_TYPE_BLIND);
public static final Set<ThingTypeUID> THERMOSTAT_THING_TYPES_UIDS = Set.of(THING_TYPE_THERMOSTAT);
public static final Set<ThingTypeUID> METER_THING_TYPES_UIDS = Set.of(THING_TYPE_ENERGYMETER_LIVE,
THING_TYPE_ENERGYMETER, THING_TYPE_GASMETER, THING_TYPE_WATERMETER);
public static final Set<ThingTypeUID> ACCESS_THING_TYPES_UIDS = Set.of(THING_TYPE_ACCESS,
THING_TYPE_ACCESS_RINGANDCOMEIN);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(BRIDGE_THING_TYPES_UIDS.stream(),
ACTION_THING_TYPES_UIDS.stream(), THERMOSTAT_THING_TYPES_UIDS.stream(), METER_THING_TYPES_UIDS.stream(),
ACCESS_THING_TYPES_UIDS.stream()).flatMap(i -> i).collect(Collectors.toSet());
// List of all Channel ids
public static final String CHANNEL_BUTTON = "button";
@ -73,6 +81,19 @@ public class NikoHomeControlBindingConstants {
public static final String CHANNEL_HEATING_DEMAND = "heatingdemand";
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_ENERGY = "energy";
public static final String CHANNEL_GAS = "gas";
public static final String CHANNEL_WATER = "water";
public static final String CHANNEL_ENERGY_DAY = "energyday";
public static final String CHANNEL_GAS_DAY = "gasday";
public static final String CHANNEL_WATER_DAY = "waterday";
public static final String CHANNEL_ENERGY_LAST = "energylast";
public static final String CHANNEL_GAS_LAST = "gaslast";
public static final String CHANNEL_WATER_LAST = "waterlast";
public static final String CHANNEL_BELL_BUTTON = "bellbutton";
public static final String CHANNEL_RING_AND_COME_IN = "ringandcomein";
public static final String CHANNEL_LOCK = "lock";
public static final String CHANNEL_ALARM = "alarm";
public static final String CHANNEL_NOTICE = "notice";
@ -87,11 +108,15 @@ public class NikoHomeControlBindingConstants {
// Thing config properties
public static final String CONFIG_ACTION_ID = "actionId";
public static final String CONFIG_STEP_VALUE = "step";
public static final String CONFIG_INVERT = "invert";
public static final String CONFIG_THERMOSTAT_ID = "thermostatId";
public static final String CONFIG_OVERRULETIME = "overruleTime";
public static final String CONFIG_ENERGYMETER_ID = "energyMeterId";
public static final String METER_ID = "meterId";
public static final String CONFIG_METER_REFRESH = "refresh";
public static final String CONFIG_ACCESS_ID = "accessId";
// Thing properties
public static final String PROPERTY_DEVICE_TYPE = "deviceType";

View File

@ -16,10 +16,11 @@ import static org.openhab.binding.nikohomecontrol.internal.NikoHomeControlBindin
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlAccessHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlActionHandler;
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.NikoHomeControlMeterHandler;
import org.openhab.binding.nikohomecontrol.internal.handler.NikoHomeControlThermostatHandler;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.net.NetworkAddressService;
@ -68,12 +69,14 @@ public class NikoHomeControlHandlerFactory extends BaseThingHandlerFactory {
} else {
return new NikoHomeControlBridgeHandler1((Bridge) thing, timeZoneProvider);
}
} 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);
} else if (THERMOSTAT_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlThermostatHandler(thing);
} else if (METER_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlMeterHandler(thing);
} else if (ACCESS_THING_TYPES_UIDS.contains(thing.getThingTypeUID())) {
return new NikoHomeControlAccessHandler(thing);
}
return null;

View File

@ -22,8 +22,10 @@ import java.util.concurrent.TimeUnit;
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.handler.NikoHomeControlBridgeHandler2;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService;
@ -78,79 +80,121 @@ public class NikoHomeControlDiscoveryService
}
logger.debug("getting devices on {}", thingHandler.getThing().getUID().getId());
discoverActionDevices(thingHandler, nhcComm);
discoverThermostatDevices(thingHandler, nhcComm);
discoverMeterDevices(thingHandler, nhcComm);
discoverAccessDevices(thingHandler, nhcComm);
}
private void discoverActionDevices(NikoHomeControlBridgeHandler bridgeHandler,
NikoHomeControlCommunication nhcComm) {
Map<String, NhcAction> actions = nhcComm.getActions();
actions.forEach((actionId, nhcAction) -> {
actions.forEach((deviceId, nhcAction) -> {
String thingName = nhcAction.getName();
String thingLocation = nhcAction.getLocation();
switch (nhcAction.getType()) {
case TRIGGER:
addActionDevice(new ThingUID(THING_TYPE_PUSHBUTTON, thingHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
addDevice(new ThingUID(THING_TYPE_PUSHBUTTON, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACTION_ID, deviceId, thingName, thingLocation);
break;
case RELAY:
addActionDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, thingHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
addDevice(new ThingUID(THING_TYPE_ON_OFF_LIGHT, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACTION_ID, deviceId, thingName, thingLocation);
break;
case DIMMER:
addActionDevice(new ThingUID(THING_TYPE_DIMMABLE_LIGHT, thingHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
addDevice(new ThingUID(THING_TYPE_DIMMABLE_LIGHT, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACTION_ID, deviceId, thingName, thingLocation);
break;
case ROLLERSHUTTER:
addActionDevice(new ThingUID(THING_TYPE_BLIND, thingHandler.getThing().getUID(), actionId),
actionId, thingName, thingLocation);
addDevice(new ThingUID(THING_TYPE_BLIND, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACTION_ID, deviceId, thingName, thingLocation);
break;
default:
logger.debug("unrecognized action type {} for {} {}", nhcAction.getType(), actionId, thingName);
logger.debug("unrecognized action type {} for {} {}", nhcAction.getType(), deviceId, thingName);
}
});
}
private void discoverThermostatDevices(NikoHomeControlBridgeHandler bridgeHandler,
NikoHomeControlCommunication nhcComm) {
Map<String, NhcThermostat> thermostats = nhcComm.getThermostats();
thermostats.forEach((thermostatId, nhcThermostat) -> {
thermostats.forEach((deviceId, nhcThermostat) -> {
String thingName = nhcThermostat.getName();
String thingLocation = nhcThermostat.getLocation();
addThermostatDevice(new ThingUID(THING_TYPE_THERMOSTAT, thingHandler.getThing().getUID(), thermostatId),
thermostatId, thingName, thingLocation);
});
Map<String, NhcEnergyMeter> energyMeters = nhcComm.getEnergyMeters();
energyMeters.forEach((energyMeterId, nhcEnergyMeter) -> {
String thingName = nhcEnergyMeter.getName();
String thingLocation = nhcEnergyMeter.getLocation();
addEnergyMeterDevice(new ThingUID(THING_TYPE_ENERGYMETER, thingHandler.getThing().getUID(), energyMeterId),
energyMeterId, thingName, thingLocation);
addDevice(new ThingUID(THING_TYPE_THERMOSTAT, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_THERMOSTAT_ID, deviceId, thingName, thingLocation);
});
}
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)
.withRepresentationProperty(CONFIG_ACTION_ID);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
private void discoverMeterDevices(NikoHomeControlBridgeHandler bridgeHandler,
NikoHomeControlCommunication nhcComm) {
if (bridgeHandler instanceof NikoHomeControlBridgeHandler2) {
// disable discovery of NHC II energy meters to avoid overload in Niko Home Control cloud, can be removed
// when Niko solves their issue with the controller sending all live power data to their cloud
return;
}
thingDiscovered(discoveryResultBuilder.build());
Map<String, NhcMeter> meters = nhcComm.getMeters();
meters.forEach((deviceId, nhcMeter) -> {
String thingName = nhcMeter.getName();
String thingLocation = nhcMeter.getLocation();
switch (nhcMeter.getType()) {
case ENERGY_LIVE:
addDevice(new ThingUID(THING_TYPE_ENERGYMETER_LIVE, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
break;
case ENERGY:
addDevice(new ThingUID(THING_TYPE_ENERGYMETER, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
break;
case GAS:
addDevice(new ThingUID(THING_TYPE_GASMETER, bridgeHandler.getThing().getUID(), deviceId), METER_ID,
deviceId, thingName, thingLocation);
break;
case WATER:
addDevice(new ThingUID(THING_TYPE_WATERMETER, bridgeHandler.getThing().getUID(), deviceId),
METER_ID, deviceId, thingName, thingLocation);
break;
default:
logger.debug("unrecognized meter type {} for {} {}", nhcMeter.getType(), deviceId, thingName);
}
});
}
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)
.withRepresentationProperty(CONFIG_THERMOSTAT_ID);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}
thingDiscovered(discoveryResultBuilder.build());
private void discoverAccessDevices(NikoHomeControlBridgeHandler bridgeHandler,
NikoHomeControlCommunication nhcComm) {
Map<String, NhcAccess> accessDevices = nhcComm.getAccessDevices();
accessDevices.forEach((deviceId, nhcAccess) -> {
String thingName = nhcAccess.getName();
String thingLocation = nhcAccess.getLocation();
switch (nhcAccess.getType()) {
case BASE:
case BELLBUTTON:
addDevice(new ThingUID(THING_TYPE_ACCESS, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACCESS_ID, deviceId, thingName, thingLocation);
break;
case RINGANDCOMEIN:
addDevice(
new ThingUID(THING_TYPE_ACCESS_RINGANDCOMEIN, bridgeHandler.getThing().getUID(), deviceId),
CONFIG_ACCESS_ID, deviceId, thingName, thingLocation);
break;
default:
logger.debug("unrecognized access type {} for {} {}", nhcAccess.getType(), deviceId, thingName);
}
});
}
private void addEnergyMeterDevice(ThingUID uid, String energyMeterId, String thingName,
private void addDevice(ThingUID uid, String deviceIdKey, String deviceId, String thingName,
@Nullable String thingLocation) {
DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID)
.withLabel(thingName).withProperty(CONFIG_ENERGYMETER_ID, energyMeterId)
.withRepresentationProperty(CONFIG_ENERGYMETER_ID);
.withLabel(thingName).withProperty(deviceIdKey, deviceId).withRepresentationProperty(deviceIdKey);
if (thingLocation != null) {
discoveryResultBuilder.withProperty("Location", thingLocation);
}

View File

@ -15,11 +15,11 @@ package org.openhab.binding.nikohomecontrol.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NikoHomeControlEnergyMeterConfig} is the config class for Niko Home Control Thermostats.
* {@link NikoHomeControlAccessConfig} is the general config class for Niko Home Control Door Locks.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlEnergyMeterConfig {
public String energyMeterId = "";
public class NikoHomeControlAccessConfig {
public String accessId = "";
}

View File

@ -0,0 +1,298 @@
/**
* Copyright (c) 2010-2024 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.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.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAccessEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.AccessType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAccess2;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlAccessHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlAccessHandler extends NikoHomeControlBaseHandler implements NhcAccessEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlAccessHandler.class);
private volatile @Nullable NhcAccess nhcAccess;
public NikoHomeControlAccessHandler(Thing thing) {
super(thing);
}
@Override
void handleCommandSelection(ChannelUID channelUID, Command command) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
logger.debug("handle command {} for {}", command, channelUID);
switch (channelUID.getId()) {
case CHANNEL_BELL_BUTTON:
if (REFRESH.equals(command)) {
accessBellEvent(nhcAccess.getBellState());
} else {
handleBellButtonCommand(command);
}
break;
case CHANNEL_RING_AND_COME_IN:
if (REFRESH.equals(command)) {
accessRingAndComeInEvent(nhcAccess.getRingAndComeInState());
} else {
handleRingAndComeInCommand(command);
}
break;
case CHANNEL_LOCK:
if (REFRESH.equals(command)) {
accessDoorLockEvent(nhcAccess.getDoorLockState());
} else {
handleDoorLockCommand(command);
}
break;
default:
logger.debug("unexpected command for channel {}", channelUID.getId());
}
}
private void handleBellButtonCommand(Command command) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.ON.equals(s)) {
nhcAccess.executeBell();
}
}
}
private void handleRingAndComeInCommand(Command command) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
nhcAccess.executeRingAndComeIn(OnOffType.ON.equals(s));
}
}
private void handleDoorLockCommand(Command command) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
if (command instanceof OnOffType) {
OnOffType s = (OnOffType) command;
if (OnOffType.OFF.equals(s)) {
nhcAccess.executeUnlock();
}
}
}
@Override
public void initialize() {
initialized = false;
NikoHomeControlAccessConfig config = getConfig().as(NikoHomeControlAccessConfig.class);
deviceId = config.accessId;
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
return;
}
updateStatus(ThingStatus.UNKNOWN);
Bridge bridge = getBridge();
if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
commStartThread = scheduler.submit(this::startCommunication);
}
}
@Override
synchronized void startCommunication() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
return;
}
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
NhcAccess nhcAccess = nhcComm.getAccessDevices().get(deviceId);
if (nhcAccess == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.deviceId");
return;
}
nhcAccess.setEventHandler(this);
updateProperties(nhcAccess);
String location = nhcAccess.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(location);
}
this.nhcAccess = nhcAccess;
initialized = true;
deviceInitialized();
}
@Override
void refresh() {
NhcAccess access = nhcAccess;
if (access != null) {
accessBellEvent(access.getBellState());
accessDoorLockEvent(access.getDoorLockState());
if (access.getType().equals(AccessType.RINGANDCOMEIN)) {
accessRingAndComeInEvent(access.getRingAndComeInState());
}
}
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
NhcAccess access = nhcComm.getAccessDevices().get(deviceId);
if (access != null) {
access.unsetEventHandler();
}
}
nhcAccess = null;
super.dispose();
}
private void updateProperties(NhcAccess nhcAccess) {
Map<String, String> properties = new HashMap<>();
if (nhcAccess instanceof NhcAccess2) {
NhcAccess2 access = (NhcAccess2) nhcAccess;
properties.put(PROPERTY_DEVICE_TYPE, access.getDeviceType());
properties.put(PROPERTY_DEVICE_TECHNOLOGY, access.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, access.getDeviceModel());
}
String buttonId = nhcAccess.getButtonId();
if (buttonId != null) {
properties.put("buttonId", buttonId);
}
if (nhcAccess.supportsVideoStream()) {
properties.put("username", "admin");
properties.put("password", "123qwe");
String ipAddress = nhcAccess.getIpAddress();
if (ipAddress != null) {
properties.put("ipAddress", ipAddress);
}
String mjpegUri = nhcAccess.getMjpegUri();
if (mjpegUri != null) {
properties.put("mjpegUri", mjpegUri);
}
String tnUri = nhcAccess.getTnUri();
if (tnUri != null) {
properties.put("tnUri", tnUri);
}
}
thing.setProperties(properties);
}
@Override
public void accessBellEvent(boolean state) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
updateState(CHANNEL_BELL_BUTTON, OnOffType.from(state));
updateStatus(ThingStatus.ONLINE);
}
@Override
public void accessRingAndComeInEvent(boolean state) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
updateState(CHANNEL_RING_AND_COME_IN, OnOffType.from(state));
updateStatus(ThingStatus.ONLINE);
}
@Override
public void accessDoorLockEvent(boolean state) {
NhcAccess nhcAccess = this.nhcAccess;
if (nhcAccess == null) {
logger.debug("access device with ID {} not initialized", deviceId);
return;
}
updateState(CHANNEL_LOCK, OnOffType.from(state));
updateStatus(ThingStatus.ONLINE);
}
@Override
public void updateVideoDeviceProperties() {
NhcAccess access = nhcAccess;
if (access != null) {
updateProperties(access);
}
}
}

View File

@ -36,8 +36,6 @@ 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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,15 +47,12 @@ import org.slf4j.LoggerFactory;
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlActionHandler extends BaseThingHandler implements NhcActionEvent {
public class NikoHomeControlActionHandler extends NikoHomeControlBaseHandler implements NhcActionEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlActionHandler.class);
private volatile @Nullable NhcAction nhcAction;
private volatile boolean initialized = false;
private String actionId = "";
private int stepValue;
private boolean invert;
@ -66,29 +61,10 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
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) {
void handleCommandSelection(ChannelUID channelUID, Command command) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
logger.debug("action with ID {} not initialized", deviceId);
return;
}
@ -103,15 +79,12 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
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:
logger.debug("unexpected command for channel {}", channelUID.getId());
@ -121,7 +94,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
private void handleSwitchCommand(Command command) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
logger.debug("action with ID {} not initialized", deviceId);
return;
}
@ -137,7 +110,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
private void handleBrightnessCommand(Command command) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
logger.debug("action with ID {} not initialized", deviceId);
return;
}
@ -177,7 +150,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
private void handleRollershutterCommand(Command command) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
logger.debug("action with ID {} not initialized", deviceId);
return;
}
@ -209,7 +182,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
} else {
config = getConfig().as(NikoHomeControlActionConfig.class);
}
actionId = config.actionId;
deviceId = config.actionId;
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
@ -224,11 +197,12 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(this::startCommunication);
commStartThread = scheduler.submit(this::startCommunication);
}
}
private synchronized void startCommunication() {
@Override
synchronized void startCommunication() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
@ -241,10 +215,18 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
return;
}
NhcAction nhcAction = nhcComm.getActions().get(actionId);
NhcAction nhcAction = nhcComm.getActions().get(deviceId);
if (nhcAction == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.actionId");
"@text/offline.configuration-error.deviceId");
return;
}
ActionType actionType = nhcAction.getType();
if (!(ActionType.TRIGGER.equals(actionType) || ActionType.RELAY.equals(actionType)
|| ActionType.DIMMER.equals(actionType) || ActionType.ROLLERSHUTTER.equals(actionType))) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.actionType");
return;
}
@ -259,25 +241,23 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
this.nhcAction = nhcAction;
logger.debug("action initialized {}", actionId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
actionEvent(nhcAction.getState());
initialized = true;
deviceInitialized();
}
@Override
void refresh() {
NhcAction action = nhcAction;
if (action != null) {
actionEvent(action.getState());
}
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
NhcAction action = nhcComm.getActions().get(actionId);
NhcAction action = nhcComm.getActions().get(deviceId);
if (action != null) {
action.unsetEventHandler();
}
@ -308,7 +288,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
public void actionEvent(int actionState) {
NhcAction nhcAction = this.nhcAction;
if (nhcAction == null) {
logger.debug("action with ID {} not initialized", actionId);
logger.debug("action with ID {} not initialized", deviceId);
return;
}
@ -332,66 +312,7 @@ public class NikoHomeControlActionHandler extends BaseThingHandler implements Nh
updateStatus(ThingStatus.ONLINE);
break;
default:
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.actionType");
}
}
@Override
public void actionInitialized() {
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void actionRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.actionRemoved");
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.scheduleRestartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
}
}
private @Nullable NikoHomeControlCommunication getCommunication(
@Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
if (ThingStatus.ONLINE.equals(bridgeStatus)) {
if (!initialized) {
scheduler.submit(this::startCommunication);
} else {
updateStatus(ThingStatus.ONLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
break;
}
}
}

View File

@ -0,0 +1,156 @@
/**
* Copyright (c) 2010-2024 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.concurrent.Future;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcBaseEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlBaseHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NikoHomeControlBaseHandler extends BaseThingHandler implements NhcBaseEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlBaseHandler.class);
volatile boolean initialized = false;
String deviceId = "";
@Nullable
Future<?> commStartThread;
public NikoHomeControlBaseHandler(Thing thing) {
super(thing);
}
@Override
public void dispose() {
if (commStartThread != null) {
commStartThread.cancel(true);
commStartThread = null;
}
super.dispose();
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
return;
}
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
handleCommandSelection(channelUID, command);
}
});
}
abstract void handleCommandSelection(ChannelUID channelUID, Command command);
@Override
public void deviceInitialized() {
logger.debug("device initialized {}", deviceId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
refresh();
}
abstract void refresh();
@Override
public void deviceRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.deviceRemoved");
}
abstract void startCommunication();
void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.scheduleRestartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED,
"@text/offline.bridge-unitialized");
}
}
@Nullable
NikoHomeControlCommunication getCommunication(@Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
}
@Nullable
NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
if (ThingStatus.ONLINE.equals(bridgeStatus)) {
if (!initialized) {
if (commStartThread != null) {
commStartThread.cancel(true);
}
commStartThread = scheduler.submit(this::startCommunication);
} else {
updateStatus(ThingStatus.ONLINE);
refresh();
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
}

View File

@ -176,6 +176,7 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
NikoHomeControlCommunication comm = nhcComm;
if (comm != null) {
comm.stopAllMeters();
comm.stopCommunication();
}
nhcComm = null;

View File

@ -1,283 +0,0 @@
/**
* Copyright (c) 2010-2024 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.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.Units;
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.ThingStatusInfo;
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 @Nullable NhcEnergyMeter nhcEnergyMeter;
private volatile boolean initialized = false;
private String energyMeterId = "";
public NikoHomeControlEnergyMeterHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NhcEnergyMeter nhcEnergyMeter = this.nhcEnergyMeter;
if (nhcEnergyMeter == null) {
logger.debug("energy meter with ID {} not initialized", energyMeterId);
return;
}
if (REFRESH.equals(command)) {
energyMeterEvent(nhcEnergyMeter.getPower());
}
}
@Override
public void initialize() {
initialized = false;
NikoHomeControlEnergyMeterConfig config = getConfig().as(NikoHomeControlEnergyMeterConfig.class);
energyMeterId = config.energyMeterId;
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
return;
}
updateStatus(ThingStatus.UNKNOWN);
Bridge bridge = getBridge();
if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(this::startCommunication);
}
}
private synchronized void startCommunication() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
return;
}
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
NhcEnergyMeter nhcEnergyMeter = nhcComm.getEnergyMeters().get(energyMeterId);
if (nhcEnergyMeter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.energyMeterId");
return;
}
nhcEnergyMeter.setEventHandler(this);
updateProperties(nhcEnergyMeter);
String location = nhcEnergyMeter.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(location);
}
this.nhcEnergyMeter = nhcEnergyMeter;
logger.debug("energy meter intialized {}", energyMeterId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
// 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);
}
initialized = true;
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
nhcComm.stopEnergyMeter(energyMeterId);
NhcEnergyMeter energyMeter = nhcComm.getEnergyMeters().get(energyMeterId);
if (energyMeter != null) {
energyMeter.unsetEventHandler();
}
}
nhcEnergyMeter = null;
super.dispose();
}
private void updateProperties(NhcEnergyMeter nhcEnergyMeter) {
Map<String, String> properties = new HashMap<>();
if (nhcEnergyMeter instanceof NhcEnergyMeter2 energyMeter) {
properties.put(PROPERTY_DEVICE_TYPE, energyMeter.getDeviceType());
properties.put(PROPERTY_DEVICE_TECHNOLOGY, energyMeter.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, energyMeter.getDeviceModel());
}
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, Units.WATT));
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void energyMeterInitialized() {
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void energyMeterRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.energyMeterRemoved");
}
@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(getBridgeHandler());
if (nhcComm != null) {
// 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(getBridgeHandler());
if (nhcComm != null) {
// 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.scheduleRestartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
}
}
private @Nullable NikoHomeControlCommunication getCommunication(
@Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
if (ThingStatus.ONLINE.equals(bridgeStatus)) {
if (!initialized) {
scheduler.submit(this::startCommunication);
} else {
updateStatus(ThingStatus.ONLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
}

View File

@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
/**
* {@link NikoHomeControlMeterConfig} is the config class for Niko Home Control Meters.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlMeterConfig {
public String meterId = "";
public int refresh = 10;
public boolean invert = false;
}

View File

@ -0,0 +1,330 @@
/**
* Copyright (c) 2010-2024 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.Units.KILOWATT_HOUR;
import static org.openhab.core.types.RefreshType.REFRESH;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
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.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeterEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcMeter1;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcMeter2;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NikoHomeControlMeterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlMeterHandler extends NikoHomeControlBaseHandler implements NhcMeterEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlMeterHandler.class);
private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
private volatile @Nullable NhcMeter nhcMeter;
public NikoHomeControlMeterHandler(Thing thing) {
super(thing);
}
@Override
void handleCommandSelection(ChannelUID channelUID, Command command) {
NhcMeter nhcMeter = this.nhcMeter;
if (nhcMeter == null) {
logger.debug("meter with ID {} not initialized", deviceId);
return;
}
if (REFRESH.equals(command)) {
switch (channelUID.getId()) {
case CHANNEL_POWER:
meterPowerEvent(nhcMeter.getPower());
break;
case CHANNEL_ENERGY:
case CHANNEL_GAS:
case CHANNEL_WATER:
case CHANNEL_ENERGY_DAY:
case CHANNEL_GAS_DAY:
case CHANNEL_WATER_DAY:
case CHANNEL_ENERGY_LAST:
case CHANNEL_GAS_LAST:
case CHANNEL_WATER_LAST:
LocalDateTime lastReadingUTC = nhcMeter.getLastReading();
if (lastReadingUTC != null) {
meterReadingEvent(nhcMeter.getReading(), nhcMeter.getDayReading(), lastReadingUTC);
}
break;
default:
logger.debug("unexpected command for channel {}", channelUID.getId());
}
}
}
@Override
public void initialize() {
initialized = false;
NikoHomeControlMeterConfig config = getConfig().as(NikoHomeControlMeterConfig.class);
deviceId = config.meterId;
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
return;
}
updateStatus(ThingStatus.UNKNOWN);
Bridge bridge = getBridge();
if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
commStartThread = scheduler.submit(this::startCommunication);
}
}
@Override
synchronized void startCommunication() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
return;
}
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
NhcMeter nhcMeter = nhcComm.getMeters().get(deviceId);
if (nhcMeter == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.deviceId");
return;
}
MeterType meterType = nhcMeter.getType();
if (!(MeterType.ENERGY_LIVE.equals(meterType) || MeterType.ENERGY.equals(meterType)
|| MeterType.GAS.equals(meterType) || MeterType.WATER.equals(meterType))) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.meterType");
return;
}
nhcMeter.setEventHandler(this);
updateProperties(nhcMeter);
String location = nhcMeter.getLocation();
if (thing.getLocation() == null) {
thing.setLocation(location);
}
this.nhcMeter = nhcMeter;
initialized = true;
deviceInitialized();
}
@Override
void refresh() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
nhcComm.startMeter(deviceId, getConfig().as(NikoHomeControlMeterConfig.class).refresh);
// 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.startMeterLive(deviceId);
}
}
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
nhcComm.stopMeterLive(deviceId);
nhcComm.stopMeter(deviceId);
NhcMeter meter = nhcComm.getMeters().get(deviceId);
if (meter != null) {
meter.unsetEventHandler();
}
}
nhcMeter = null;
super.dispose();
}
private void updateProperties(NhcMeter nhcMeter) {
Map<String, String> properties = new HashMap<>();
if (nhcMeter instanceof NhcMeter1) {
NhcMeter1 meter = (NhcMeter1) nhcMeter;
properties.put("type", meter.getMeterType());
LocalDateTime referenceDate = meter.getReferenceDate();
if (referenceDate != null) {
properties.put("startdateUTC", referenceDate.format(DATE_TIME_FORMAT));
}
} else if (nhcMeter instanceof NhcMeter2) {
NhcMeter2 meter = (NhcMeter2) nhcMeter;
properties.put(PROPERTY_DEVICE_TYPE, meter.getDeviceType());
properties.put(PROPERTY_DEVICE_TECHNOLOGY, meter.getDeviceTechnology());
properties.put(PROPERTY_DEVICE_MODEL, meter.getDeviceModel());
}
thing.setProperties(properties);
}
@Override
public void meterPowerEvent(@Nullable Integer power) {
NhcMeter nhcMeter = this.nhcMeter;
if (nhcMeter == null) {
logger.debug("meter with ID {} not initialized", deviceId);
return;
}
MeterType meterType = nhcMeter.getType();
if (meterType != MeterType.ENERGY_LIVE) {
logger.debug("meter with ID {} does not support live readings", deviceId);
return;
}
if (power == null) {
updateState(CHANNEL_POWER, UnDefType.UNDEF);
} else {
boolean invert = getConfig().as(NikoHomeControlMeterConfig.class).invert;
int value = (invert ? -1 : 1) * power;
updateState(CHANNEL_POWER, new QuantityType<>(value, Units.WATT));
}
updateStatus(ThingStatus.ONLINE);
}
@Override
public void meterReadingEvent(double meterReading, double meterReadingDay, LocalDateTime lastReadingUTC) {
NhcMeter nhcMeter = this.nhcMeter;
if (nhcMeter == null) {
logger.debug("meter with ID {} not initialized", deviceId);
return;
}
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
if (bridgeHandler == null) {
logger.debug("Cannot update meter channels, no bridge handler");
return;
}
ZonedDateTime lastReading = lastReadingUTC.atZone(ZoneOffset.UTC)
.withZoneSameInstant(bridgeHandler.getTimeZone());
boolean invert = getConfig().as(NikoHomeControlMeterConfig.class).invert;
double value = (invert ? -1 : 1) * meterReading;
double dayValue = (invert ? -1 : 1) * meterReadingDay;
MeterType meterType = nhcMeter.getType();
switch (meterType) {
case ENERGY_LIVE:
case ENERGY:
updateState(CHANNEL_ENERGY, new QuantityType<>(value, KILOWATT_HOUR));
updateState(CHANNEL_ENERGY_DAY, new QuantityType<>(dayValue, KILOWATT_HOUR));
updateState(CHANNEL_ENERGY_LAST, new DateTimeType(lastReading));
updateStatus(ThingStatus.ONLINE);
break;
case GAS:
updateState(CHANNEL_GAS, new QuantityType<>(value, SIUnits.CUBIC_METRE));
updateState(CHANNEL_GAS_DAY, new QuantityType<>(dayValue, SIUnits.CUBIC_METRE));
updateState(CHANNEL_GAS_LAST, new DateTimeType(lastReading));
updateStatus(ThingStatus.ONLINE);
break;
case WATER:
updateState(CHANNEL_WATER, new QuantityType<>(value, SIUnits.CUBIC_METRE));
updateState(CHANNEL_WATER_DAY, new QuantityType<>(dayValue, SIUnits.CUBIC_METRE));
updateState(CHANNEL_WATER_LAST, new DateTimeType(lastReading));
updateStatus(ThingStatus.ONLINE);
break;
default:
break;
}
}
@Override
public void channelLinked(ChannelUID channelUID) {
// Subscribing to power readings starts an intensive data flow, therefore only do it when there is an item
// linked to the channel
if (!CHANNEL_POWER.equals(channelUID.getId())) {
return;
}
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.startMeterLive(deviceId);
}
});
}
}
@Override
public void channelUnlinked(ChannelUID channelUID) {
if (!CHANNEL_POWER.equals(channelUID.getId())) {
return;
}
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
// This can be expensive, therefore do it in a job.
scheduler.submit(() -> {
if (!nhcComm.communicationActive()) {
restartCommunication(nhcComm);
}
if (nhcComm.communicationActive()) {
nhcComm.stopMeterLive(deviceId);
// as this is momentary power production/consumption, we set it UNDEF as we do not get readings
// anymore
updateState(CHANNEL_POWER, UnDefType.UNDEF);
}
});
}
}
}

View File

@ -37,8 +37,6 @@ 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.ThingStatusInfo;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -50,15 +48,12 @@ import org.slf4j.LoggerFactory;
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NikoHomeControlThermostatHandler extends BaseThingHandler implements NhcThermostatEvent {
public class NikoHomeControlThermostatHandler extends NikoHomeControlBaseHandler implements NhcThermostatEvent {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlThermostatHandler.class);
private volatile @Nullable NhcThermostat nhcThermostat;
private volatile boolean initialized = false;
private String thermostatId = "";
private int overruleTime;
private volatile @Nullable ScheduledFuture<?> refreshTimer; // used to refresh the remaining overrule time every
@ -69,29 +64,10 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
logger.debug("communication not up yet, cannot handle command {} for {}", command, channelUID);
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) {
void handleCommandSelection(ChannelUID channelUID, Command command) {
NhcThermostat nhcThermostat = this.nhcThermostat;
if (nhcThermostat == null) {
logger.debug("thermostat with ID {} not initialized", thermostatId);
logger.debug("thermostat with ID {} not initialized", deviceId);
return;
}
@ -107,19 +83,16 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
case CHANNEL_MEASURED:
case CHANNEL_DEMAND:
case CHANNEL_HEATING_DEMAND:
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_MODE:
if (command instanceof DecimalType decimalCommand) {
nhcThermostat.executeMode(decimalCommand.intValue());
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_HEATING_MODE:
if (command instanceof StringType) {
nhcThermostat.executeMode(command.toString());
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_SETPOINT:
// Always set the new setpoint temperature as an overrule
@ -137,7 +110,6 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
BigDecimal setpoint = decimalCommand.toBigDecimal();
nhcThermostat.executeOverrule(Math.round(setpoint.floatValue() * 10), time);
}
updateStatus(ThingStatus.ONLINE);
break;
case CHANNEL_OVERRULETIME:
if (command instanceof DecimalType decimalCommand) {
@ -149,7 +121,6 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
}
nhcThermostat.executeOverrule(overrule, overruletime);
}
updateStatus(ThingStatus.ONLINE);
break;
default:
logger.debug("unexpected command for channel {}", channelUID.getId());
@ -162,7 +133,7 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
NikoHomeControlThermostatConfig config = getConfig().as(NikoHomeControlThermostatConfig.class);
thermostatId = config.thermostatId;
deviceId = config.thermostatId;
overruleTime = config.overruleTime;
NikoHomeControlBridgeHandler bridgeHandler = getBridgeHandler();
@ -178,11 +149,12 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
if ((bridge != null) && ThingStatus.ONLINE.equals(bridge.getStatus())) {
// We need to do this in a separate thread because we may have to wait for the
// communication to become active
scheduler.submit(this::startCommunication);
commStartThread = scheduler.submit(this::startCommunication);
}
}
private synchronized void startCommunication() {
@Override
synchronized void startCommunication() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm == null) {
@ -195,10 +167,10 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
return;
}
NhcThermostat nhcThermostat = nhcComm.getThermostats().get(thermostatId);
NhcThermostat nhcThermostat = nhcComm.getThermostats().get(deviceId);
if (nhcThermostat == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.thermostatId");
"@text/offline.configuration-error.deviceId");
return;
}
@ -213,26 +185,24 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
this.nhcThermostat = nhcThermostat;
logger.debug("thermostat intialized {}", thermostatId);
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
thermostatEvent(nhcThermostat.getMeasured(), nhcThermostat.getSetpoint(), nhcThermostat.getMode(),
nhcThermostat.getOverrule(), nhcThermostat.getDemand());
initialized = true;
deviceInitialized();
}
@Override
void refresh() {
NhcThermostat thermostat = nhcThermostat;
if (thermostat != null) {
thermostatEvent(thermostat.getMeasured(), thermostat.getSetpoint(), thermostat.getMode(),
thermostat.getOverrule(), thermostat.getDemand());
}
}
@Override
public void dispose() {
NikoHomeControlCommunication nhcComm = getCommunication(getBridgeHandler());
if (nhcComm != null) {
NhcThermostat thermostat = nhcComm.getThermostats().get(thermostatId);
NhcThermostat thermostat = nhcComm.getThermostats().get(deviceId);
if (thermostat != null) {
thermostat.unsetEventHandler();
}
@ -257,7 +227,7 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
public void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand) {
NhcThermostat nhcThermostat = this.nhcThermostat;
if (nhcThermostat == null) {
logger.debug("thermostat with ID {} not initialized", thermostatId);
logger.debug("thermostat with ID {} not initialized", deviceId);
return;
}
@ -289,7 +259,6 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
* Method to update state of overruletime channel every minute with remaining time.
*
* @param NhcThermostat object
*
*/
private void scheduleRefreshOverruletime(NhcThermostat nhcThermostat) {
cancelRefreshTimer();
@ -314,62 +283,4 @@ public class NikoHomeControlThermostatHandler extends BaseThingHandler implement
}
refreshTimer = null;
}
@Override
public void thermostatInitialized() {
Bridge bridge = getBridge();
if ((bridge != null) && (bridge.getStatus() == ThingStatus.ONLINE)) {
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void thermostatRemoved() {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.thermostatRemoved");
}
private void restartCommunication(NikoHomeControlCommunication nhcComm) {
// We lost connection but the connection object is there, so was correctly started.
// Try to restart communication.
nhcComm.scheduleRestartCommunication();
// If still not active, take thing offline and return.
if (!nhcComm.communicationActive()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/offline.communication-error");
return;
}
// Also put the bridge back online
NikoHomeControlBridgeHandler nhcBridgeHandler = getBridgeHandler();
if (nhcBridgeHandler != null) {
nhcBridgeHandler.bridgeOnline();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error.invalid-bridge-handler");
}
}
private @Nullable NikoHomeControlCommunication getCommunication(
@Nullable NikoHomeControlBridgeHandler nhcBridgeHandler) {
return nhcBridgeHandler != null ? nhcBridgeHandler.getCommunication() : null;
}
private @Nullable NikoHomeControlBridgeHandler getBridgeHandler() {
Bridge nhcBridge = getBridge();
return nhcBridge != null ? (NikoHomeControlBridgeHandler) nhcBridge.getHandler() : null;
}
@Override
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
ThingStatus bridgeStatus = bridgeStatusInfo.getStatus();
if (ThingStatus.ONLINE.equals(bridgeStatus)) {
if (!initialized) {
scheduler.submit(this::startCommunication);
} else {
updateStatus(ThingStatus.ONLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
}
}
}

View File

@ -0,0 +1,338 @@
/**
* Copyright (c) 2010-2024 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 static org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.NHCIDLE;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.AccessType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcAccess} class represents the access control Niko Home Control communication object. It contains all
* fields representing a Niko Home Control access control device and has methods to unlock the door in Niko Home Control
* and receive bell signals. A specific implementation is
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAccess2}.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcAccess {
private final Logger logger = LoggerFactory.getLogger(NhcAccess.class);
protected NikoHomeControlCommunication nhcComm;
protected final String id;
protected String name;
protected AccessType type;
protected @Nullable String location;
protected volatile boolean bellRinging;
protected volatile boolean ringAndComeIn;
protected volatile boolean locked;
protected @Nullable NhcVideo nhcVideo;
protected @Nullable String buttonId;
protected int buttonIndex = 1;
@Nullable
private NhcAccessEvent eventHandler;
protected NhcAccess(String id, String name, @Nullable String location, AccessType type, @Nullable String buttonId,
NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.type = type;
this.location = location;
this.nhcComm = nhcComm;
this.buttonId = buttonId;
try {
if (buttonId != null) {
int index = Integer.parseInt(buttonId.split("_")[1]);
buttonIndex = index;
}
} catch (NumberFormatException e) {
logger.debug("cannot retrieve button index from butto id {}", buttonId);
}
}
/**
* This method should be called when an object implementing the {@NhcAccessEvent} interface is initialized.
* It keeps a record of the event handler in that object so it can be updated when the access control device
* receives an update from the Niko Home Control IP-interface.
*
* @param eventHandler
*/
public void setEventHandler(NhcAccessEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* This method should be called when an object implementing the {@NhcAccessEvent} interface is disposed.
* It resets the reference, so no updates go to the handler anymore.
*/
public void unsetEventHandler() {
this.eventHandler = null;
}
/**
* Sets a link to the video phone device with the bell button that triggers this access device.
*
* @param nhcVideo if null, link to video device will be removed
*/
public void setNhcVideo(@Nullable NhcVideo nhcVideo) {
NhcVideo currentVideo = this.nhcVideo;
if ((currentVideo != null) && (nhcVideo == null)) {
currentVideo.updateState(buttonIndex, NHCIDLE);
}
this.nhcVideo = nhcVideo;
NhcAccessEvent handler = eventHandler;
if ((handler != null) && (nhcVideo != null)) {
handler.updateVideoDeviceProperties();
nhcVideo.updateState(buttonIndex, nhcVideo.getState(buttonIndex));
}
}
/**
* @return video device linked to access device
*/
public @Nullable NhcVideo getNhcVideo() {
return nhcVideo;
}
/**
* Get the id of the access control device.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of the access control device.
*
* @return access control name
*/
public String getName() {
return name;
}
/**
* Set name of the access control device.
*
* @param name access control name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get type of access control identified.
* <p>
* AccessType can be GENERIC (only doorlock), RINGANDCOMEIN (doorlock and ring and come in on/off) or BELLBUTTON
* (doorlock and bell ringing).
*
* @return {@link AccessType}
*/
public AccessType getType() {
return type;
}
/**
* Get location name of access control device.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Set location of the access control device.
*
* @param location access control location
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/**
* @return buttonId of button connected to access control action, null if no button connected
*/
public @Nullable String getButtonId() {
return buttonId;
}
/**
* Get the button index of the bell button on the video phone device that is linked to this access device.
*
* @return button index
*/
public int getButtonIndex() {
return buttonIndex;
}
/**
* @return true if the connected video phone device supports streaming video
*/
public boolean supportsVideoStream() {
NhcVideo video = nhcVideo;
return (video != null) ? video.supportsVideoStream() : false;
}
/**
* @return IP address of connected video phone
*/
public @Nullable String getIpAddress() {
NhcVideo video = nhcVideo;
return (video != null) ? video.getIpAddress() : null;
}
/**
* @return URI for MJPEG stream from connected video phone
*/
public @Nullable String getMjpegUri() {
NhcVideo video = nhcVideo;
return (video != null) ? video.getMjpegUri() : null;
}
/**
* @return URI for JPEG still images from connected video phone
*/
public @Nullable String getTnUri() {
NhcVideo video = nhcVideo;
return (video != null) ? video.getTnUri() : null;
}
/**
* Get bell state of the access control device.
*
* @return bell state
*/
public boolean getBellState() {
return bellRinging;
}
/**
* Send update of bell state through event handler to subscribers.
*
* @param state
*/
public void updateBellState(boolean state) {
bellRinging = state;
NhcAccessEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
logger.debug("update channel state for {} with {}", id, state);
eventHandler.accessBellEvent(state);
}
}
/**
* Get state of ring and come in.
*
* @return ring and come in state, true if enabled
*/
public boolean getRingAndComeInState() {
return ringAndComeIn;
}
/**
* Send update of ring and come in state through event handler to subscribers.
*
* @param state
*/
public void updateRingAndComeInState(boolean state) {
ringAndComeIn = state;
NhcAccessEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
logger.debug("update channel state for {} with {}", id, state);
eventHandler.accessRingAndComeInEvent(state);
}
}
/**
* Get state of the access control device.
*
* @return door lock state
*/
public boolean getDoorLockState() {
return locked;
}
/**
* Send update of door lock state through event handler to subscribers.
*
* @param state
*/
public void updateDoorLockState(boolean state) {
locked = state;
NhcAccessEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
logger.debug("update channel state for {} with {}", id, state);
eventHandler.accessDoorLockEvent(state);
}
}
/**
* Method called when access control device is removed from the Niko Home Control Controller.
*/
public void accessDeviceRemoved() {
logger.debug("access control device removed {}, {}", id, name);
NhcAccessEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.deviceRemoved();
unsetEventHandler();
}
NhcVideo video = nhcVideo;
if (video != null) {
video.removeNhcAccess(buttonIndex);
}
nhcVideo = null;
}
/**
* Send a ring the bell message to the Niko Home Control controller.
*/
public void executeBell() {
NhcVideo video = nhcVideo;
if (type.equals(AccessType.BELLBUTTON)) {
logger.debug("execute bell for {}", id);
nhcComm.executeAccessBell(id);
} else if (video != null) {
video.executeBell(buttonIndex);
}
}
/**
* Turn ring and come in on/off, send message to controller.
*
* @param ringAndComeIn
*/
public void executeRingAndComeIn(boolean ringAndComeIn) {
logger.debug("switch ring and come in for {} to {}", id, ringAndComeIn);
nhcComm.executeAccessRingAndComeIn(id, ringAndComeIn);
}
/**
* Send a door unlock message to the Niko Home Control controller.
*/
public void executeUnlock() {
logger.debug("execute unlock for {}", id);
nhcComm.executeAccessUnlock(id);
}
}

View File

@ -13,11 +13,9 @@
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
* The {@link NhcAccessEvent} interface is used to pass door lock 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.
@ -25,24 +23,31 @@ import org.eclipse.jdt.annotation.Nullable;
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcEnergyMeterEvent {
public interface NhcAccessEvent extends NhcBaseEvent {
/**
* This method is called when an energyMeter event is received from the Niko Home Control controller.
* This method is called when a door bell 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
* @param state true for bell ringing
*/
void energyMeterEvent(@Nullable Integer power);
void accessBellEvent(boolean state);
/**
* Called to indicate the energyMeter has been initialized.
* This method is called when a ring and come in setting event is received from the Niko Home Control controller.
*
* @param state true for bell ringing
*/
void energyMeterInitialized();
void accessRingAndComeInEvent(boolean state);
/**
* Called to indicate the energyMeter has been removed from the Niko Home Control controller.
* This method is called when a door lock event is received from the Niko Home Control controller.
*
* @param state true for locked
*/
void energyMeterRemoved();
void accessDoorLockEvent(boolean state);
/**
* Update video device properties.
*/
void updateVideoDeviceProperties();
}

View File

@ -15,15 +15,14 @@ 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}.
* updates. Specific implementation are {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcAction1}
* and {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2}.
*
* @author Mark Herwege - Initial Contribution
*/
@ -38,6 +37,7 @@ public abstract class NhcAction {
protected String name;
protected ActionType type;
protected @Nullable String location;
protected volatile int state;
protected volatile int closeTime = 0;
protected volatile int openTime = 0;
@ -197,13 +197,15 @@ public abstract class NhcAction {
logger.debug("action removed {}, {}", id, name);
NhcActionEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.actionRemoved();
eventHandler.deviceRemoved();
unsetEventHandler();
}
}
/**
* Sets state of action. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
* Sets state of action. This method is implemented in
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcAction1} and
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2}.
*
* @param state - The allowed values depend on the action type.
* switch action: 0 or 100
@ -213,7 +215,9 @@ public abstract class NhcAction {
public abstract void setState(int state);
/**
* Sends action to Niko Home Control. This method is implemented in {@link NhcAction1} and {@link NhcAction2}.
* Sends action to Niko Home Control. This method is implemented in
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcAction1} and
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcAction2}.
*
* @param command - The allowed values depend on the action type.
* switch action: On or Off

View File

@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcActionEvent {
public interface NhcActionEvent extends NhcBaseEvent {
/**
* This method is called when an action event is received from the Niko Home Control controller.
@ -31,16 +31,4 @@ public interface NhcActionEvent {
* @param state
*/
void actionEvent(int state);
/**
* Called to indicate the action has been initialized.
*
*/
void actionInitialized();
/**
* Called to indicate the action has been removed from the Niko Home Control controller.
*
*/
void actionRemoved();
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2010-2024 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 NhcBaseEvent} interface is used to pass device 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 NhcBaseEvent {
/**
* Called to indicate the device has been initialized.
*
*/
void deviceInitialized();
/**
* Called to indicate the device has been removed from the Niko Home Control controller.
*
*/
void deviceRemoved();
}

View File

@ -1,162 +0,0 @@
/**
* Copyright (c) 2010-2024 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;
protected @Nullable String location;
// 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, @Nullable String location, NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.location = location;
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("update channel for {}", id);
handler.energyMeterEvent(power);
}
}
/**
* Method called when energyMeters meter is removed from the Niko Home Control Controller.
*/
public void energyMeterRemoved() {
logger.debug("action removed {}, {}", id, name);
NhcEnergyMeterEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.energyMeterRemoved();
unsetEventHandler();
}
}
/**
* This method should be called when an object implementing the {@link 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;
}
/**
* This method should be called when an object implementing the {@link NhcEnergyMeterEvent} interface is disposed.
* It resets the reference, so no updates go to the handler anymore.
*
*/
public void unsetEventHandler() {
this.eventHandler = null;
}
/**
* Get id of meter.
*
* @return id
*/
public String getId() {
return id;
}
/**
* Get name of meter.
*
* @return energyMeter name
*/
public String getName() {
return name;
}
/**
* Set name of meter.
*
* @param name meter name
*/
public void setName(String name) {
this.name = name;
}
/**
* Get location name of meter.
*
* @return location energyMeter location
*/
public @Nullable String getLocation() {
return location;
}
/**
* Set location name of meter.
*
* @param location meter location name
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/**
* @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("update power channel for {} with {}", id, power);
handler.energyMeterEvent(power);
}
}
}

View File

@ -0,0 +1,310 @@
/**
* Copyright (c) 2010-2024 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.util.Random;
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.NikoHomeControlConstants.MeterType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcMeter} class represents the meter Niko Home Control communication object. It contains all fields
* representing a Niko Home Control meter and has methods to receive meter usage information.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcMeter {
private final Logger logger = LoggerFactory.getLogger(NhcMeter.class);
protected NikoHomeControlCommunication nhcComm;
protected final String id;
protected String name;
protected MeterType type;
protected @Nullable LocalDateTime referenceDateUTC;
protected @Nullable String location;
// This can be null as long as we do not receive power readings
protected volatile @Nullable Integer power;
protected volatile int reading;
protected volatile int dayReading;
protected volatile @Nullable LocalDateTime lastReadingUTC;
private @Nullable NhcMeterEvent eventHandler;
private ScheduledExecutorService scheduler;
private volatile @Nullable ScheduledFuture<?> restartTimer;
private volatile @Nullable ScheduledFuture<?> readingSchedule;
private Random r = new Random();
protected NhcMeter(String id, String name, MeterType type, @Nullable LocalDateTime referenceDate,
@Nullable String location, NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
this.id = id;
this.name = name;
this.type = type;
this.referenceDateUTC = referenceDate;
this.location = location;
this.nhcComm = nhcComm;
this.scheduler = scheduler;
}
/**
* Update power value of the meter without touching the meter definition (id, name) and without changing the
* ThingHandler callback.
*
*/
protected void updatePowerState() {
NhcMeterEvent handler = eventHandler;
Integer value = getPower();
if ((handler != null) && (value != null)) {
logger.debug("update power channel for {} with {}", id, value);
handler.meterPowerEvent(value);
}
}
/**
* Update meter reading value of the meter without touching the meter definition (id, name) and without changing the
* ThingHandler callback.
*
*/
protected void updateReadingState() {
NhcMeterEvent handler = eventHandler;
double reading = getReading();
double dayReading = getDayReading();
LocalDateTime lastReading = getLastReading();
if ((handler != null) && (lastReading != null)) {
logger.debug("update meter reading channels for {} with {}, day {}", id, reading, dayReading);
handler.meterReadingEvent(reading, dayReading, lastReading);
}
}
/**
* Method called when meter is removed from the Niko Home Control Controller.
*/
public void meterRemoved() {
logger.debug("meter removed {}, {}", id, name);
NhcMeterEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.deviceRemoved();
unsetEventHandler();
}
}
/**
* This method should be called when an object implementing the {@NhcMeterEvent} 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(NhcMeterEvent eventHandler) {
this.eventHandler = eventHandler;
}
/**
* This method should be called when an object implementing the {@NhcMeterEvent} interface is disposed.
* It resets the reference, so no updates go to the handler anymore.
*
*/
public void unsetEventHandler() {
this.eventHandler = null;
}
/**
* Get the id of the meter.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of the meter.
*
* @return meter name
*/
public String getName() {
return name;
}
/**
* Set name of meter.
*
* @param name meter name
*/
public void setName(String name) {
this.name = name;
}
public MeterType getType() {
return type;
}
/**
* Get location name of meter.
*
* @return location name
*/
public @Nullable String getLocation() {
return location;
}
/**
* Set location of meter.
*
* @param location meter location
*/
public void setLocation(@Nullable String location) {
this.location = location;
}
/**
* @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;
updatePowerState();
}
/**
* @return the meter reading in the base unit (m^3 for gas and water, kWh for energy)
*/
public double getReading() {
// For energy, readings are in W per 10 min, convert to kWh
// For water and gas, readings are in 0.1 dm^3, convert to m^3
return ((type == MeterType.ENERGY) || (type == MeterType.ENERGY_LIVE)) ? (reading / 6000.0)
: (reading / 10000.0);
}
/**
* @return reading without conversion, as provided by NHC
*/
public int getReadingInt() {
return reading;
}
/**
* @return the meter reading for the current day in the base unit (m^3 for gas and water, kWh for energy)
*/
public double getDayReading() {
// For energy, readings are in W per 10 min, convert to kWh
// For water and gas, readings are in 0.1 dm^3, convert to m^3
return ((type == MeterType.ENERGY) || (type == MeterType.ENERGY_LIVE)) ? (dayReading / 6000.0)
: (dayReading / 10000.0);
}
/**
* @return day reading without conversion, as provided by NHC
*/
public int getDayReadingInt() {
return dayReading;
}
/**
* @return last processed meter reading in UTC zone
*/
public @Nullable LocalDateTime getLastReading() {
return lastReadingUTC;
}
/**
* @param reading the meter reading
* @param dayReading the day meter reading
* @param lastReading the last meter reading time in UTC zone
*/
public void setReading(int reading, int dayReading, LocalDateTime lastReading) {
this.reading = reading;
this.dayReading = dayReading;
this.lastReadingUTC = lastReading;
updateReadingState();
}
/**
* Start the flow of 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 stopMeterLive} method to stop the
* flow of information.
*/
public void startMeterLive() {
stopMeterLive();
restartTimer = scheduler.scheduleWithFixedDelay(() -> {
nhcComm.retriggerMeterLive(id);
}, 0, 25, TimeUnit.SECONDS);
}
/**
* Cancel receiving energy information from the controller. We therefore stop the automatic retriggering of the
* subscription, see {@link startMeterLive}.
*/
public void stopMeterLive() {
ScheduledFuture<?> timer = restartTimer;
if (timer != null) {
timer.cancel(true);
restartTimer = null;
}
}
/**
* Start receiving meter data at regular intervals. Initial delay will be random between 10 and 100s to reduce risk
* of overloading controller during initialization.
*
* @param refresh interval between meter queries in minutes
*/
public void startMeter(int refresh) {
stopMeter();
int firstRefreshDelay = 10 + r.nextInt(90);
logger.debug("schedule meter data refresh for {} every {} minutes, first refresh in {}s", id, refresh,
firstRefreshDelay);
readingSchedule = scheduler.scheduleWithFixedDelay(() -> {
nhcComm.executeMeter(id);
}, firstRefreshDelay, refresh * 60, TimeUnit.SECONDS);
}
/**
* Stop receiving meter data.
*/
public void stopMeter() {
ScheduledFuture<?> schedule = readingSchedule;
if (schedule != null) {
schedule.cancel(true);
readingSchedule = null;
}
}
/**
* @return start date and time for meter readings in UTC zone
*/
public @Nullable LocalDateTime getReferenceDate() {
return referenceDateUTC;
}
}

View File

@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link NhcMeterEvent} interface is used to pass 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 NhcMeterEvent extends NhcBaseEvent {
/**
* This method is called when a 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
*/
void meterPowerEvent(@Nullable Integer power);
/**
* This method is called when a meter reading is received from the Niko Home Control controller.
*
* @param reading meter reading
* @param dayReading meter reading for current day
* @param lastReadingUTC last meter reading date and time, UTC
*/
void meterReadingEvent(double reading, double dayReading, LocalDateTime lastReadingUTC);
}

View File

@ -21,15 +21,15 @@ import java.util.Arrays;
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}.
* receive thermostat updates. Specific implementation are
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcThermostat1} and
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2}.
*
* @author Mark Herwege - Initial Contribution
*/
@ -40,9 +40,10 @@ public abstract class NhcThermostat {
protected NikoHomeControlCommunication nhcComm;
protected String id;
protected final String id;
protected String name;
protected @Nullable String location;
protected volatile int measured;
protected volatile int setpoint;
protected volatile int mode;
@ -75,7 +76,7 @@ public abstract class NhcThermostat {
* @param ecosave
* @param demand 0 if no demand, > 0 if heating, &lt; 0 if cooling
*/
public void updateState(int measured, int setpoint, int mode, int overrule, int overruletime, int ecosave,
public void setState(int measured, int setpoint, int mode, int overrule, int overruletime, int ecosave,
int demand) {
setMeasured(measured);
setSetpoint(setpoint);
@ -85,7 +86,7 @@ public abstract class NhcThermostat {
setEcosave(ecosave);
setDemand(demand);
updateChannels();
updateState();
}
/**
@ -95,12 +96,12 @@ public abstract class NhcThermostat {
logger.debug("action removed {}, {}", id, name);
NhcThermostatEvent eventHandler = this.eventHandler;
if (eventHandler != null) {
eventHandler.thermostatRemoved();
eventHandler.deviceRemoved();
unsetEventHandler();
}
}
private void updateChannels() {
protected void updateState() {
NhcThermostatEvent handler = eventHandler;
if (handler != null) {
logger.debug("update channels for {}", id);
@ -285,8 +286,9 @@ public abstract class NhcThermostat {
}
/**
* Sends thermostat mode to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
* Sends thermostat mode to Niko Home Control. This method is implemented in
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcThermostat1} and
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2}.
*
* @param mode
*/
@ -307,8 +309,9 @@ public abstract class NhcThermostat {
}
/**
* Sends thermostat setpoint to Niko Home Control. This method is implemented in {@link NhcThermostat1} and
* {@link NhcThermostat2}.
* Sends thermostat setpoint to Niko Home Control. This method is implemented in
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NhcThermostat1} and
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcThermostat2}.
*
* @param overrule temperature to overrule the setpoint in 0.1°C multiples
* @param overruletime time duration in min for overrule

View File

@ -24,7 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public interface NhcThermostatEvent {
public interface NhcThermostatEvent extends NhcBaseEvent {
/**
* This method is called when thermostat event is received from the Niko Home Control controller.
@ -36,16 +36,4 @@ public interface NhcThermostatEvent {
* @param demand 0 if no demand, > 0 if heating, &lt; 0 if cooling
*/
void thermostatEvent(int measured, int setpoint, int mode, int overrule, int demand);
/**
* Called to indicate the thermostat has been initialized.
*
*/
void thermostatInitialized();
/**
* Called to indicate the thermostat has been removed from the Niko Home Control controller.
*
*/
void thermostatRemoved();
}

View File

@ -0,0 +1,211 @@
/**
* Copyright (c) 2010-2024 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 java.net.UnknownHostException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcVideo} class represents a Niko Home Control 2 video door station device. It is used in conjunction
* with NhcAccess2 to capture the bell signal on a video door station for access control.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public abstract class NhcVideo {
private final Logger logger = LoggerFactory.getLogger(NhcVideo.class);
protected NikoHomeControlCommunication nhcComm;
private final String id;
private String name;
private @Nullable String macAddress;
protected Map<Integer, String> callStatus = new ConcurrentHashMap<>();
protected Map<Integer, NhcAccess> nhcAccessMap = new ConcurrentHashMap<>();
private boolean supportsVideoStream;
private @Nullable String ipAddress;
private @Nullable String mjpegUri;
private @Nullable String tnUri;
protected NhcVideo(String id, String name, @Nullable String macAddress, @Nullable String ipAddress,
@Nullable String mjpegUri, @Nullable String tnUri, boolean supportsVideoStream,
NikoHomeControlCommunication nhcComm) {
this.id = id;
this.name = name;
this.nhcComm = nhcComm;
this.macAddress = macAddress;
if (ipAddress != null) {
try {
// receiving IPv4 address as string with format L.00, convert to classic format for ip address
this.ipAddress = InetAddress.getByName(ipAddress.split("\\.")[0]).getHostAddress();
} catch (UnknownHostException e) {
logger.debug("invalid ip address {} for video device {}", ipAddress, id);
}
}
this.mjpegUri = mjpegUri;
this.tnUri = tnUri;
this.supportsVideoStream = supportsVideoStream;
}
@Nullable
NhcAccess getNhcAccess(int buttonIndex) {
return nhcAccessMap.get(buttonIndex);
}
/**
* Create a link between a specific video device button and an access device
*
* @param buttonIndex
* @param nhcAccess
*/
public void setNhcAccess(int buttonIndex, NhcAccess nhcAccess) {
nhcAccessMap.put(buttonIndex, nhcAccess);
}
/**
* Remove the link to an access device for a specific video device button, to be called when an access device is
* removed from the system.
*
* @param buttonIndex
*/
public void removeNhcAccess(int buttonIndex) {
nhcAccessMap.remove(buttonIndex);
}
/**
* @return MAC address of the video device
*/
public @Nullable String getMacAddress() {
return macAddress;
}
/**
* @return IP address of the video device
*/
public @Nullable String getIpAddress() {
return ipAddress;
}
/**
* @return URI for the MJPEG stream on the video device
*/
public @Nullable String getMjpegUri() {
return mjpegUri;
}
/**
* @return URI for static images on the video device
*/
public @Nullable String getTnUri() {
return tnUri;
}
/**
* Get the id of the access control device.
*
* @return the id
*/
public String getId() {
return id;
}
/**
* Get name of the access control device.
*
* @return access control name
*/
public String getName() {
return name;
}
/**
* Set name of the access control device.
*
* @param name access control name
*/
public void setName(String name) {
this.name = name;
}
/**
* @return true if the video device supports streaming video
*/
public boolean supportsVideoStream() {
return supportsVideoStream;
}
/**
* Method called when video device is removed from the Niko Home Control Controller.
*/
public void videoDeviceRemoved() {
nhcAccessMap.forEach((buttonIndex, nhcAccess) -> {
nhcAccess.setNhcVideo(null);
});
nhcAccessMap.clear();
logger.debug("video device removed {}, {}", id, name);
}
/**
* Ring the bell, send bell message to the controller
*
* @param buttonIndex
*/
public void executeBell(int buttonIndex) {
logger.debug("execute video bell {} for button {}", id, buttonIndex);
nhcComm.executeVideoBell(id, buttonIndex);
}
/**
* @param buttonIndex
* @return state of button, values can be Idle, Ringing or Active
*/
public @Nullable String getState(int buttonIndex) {
return callStatus.get(buttonIndex);
}
/**
* Update the call state with the call state received for a specific button
*
* @param buttonIndex
* @param callStatus
*/
public abstract void updateState(int buttonIndex, @Nullable String callStatus);
/**
* Update the call state with the call state received for a maximum of 4 buttons on the video device.
*
* @param callStatus01
* @param callStatus02
* @param callStatus03
* @param callStatus04
*/
public void updateState(@Nullable String callStatus01, @Nullable String callStatus02, @Nullable String callStatus03,
@Nullable String callStatus04) {
updateState(1, callStatus01);
updateState(2, callStatus02);
updateState(3, callStatus03);
updateState(4, callStatus04);
}
}

View File

@ -21,14 +21,14 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
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
* Niko Home Control System.
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc1.NikoHomeControlCommunication1} or
* {@link org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NikoHomeControlCommunication2} should be
* used for the respective version of Niko Home Control.
* <ul>
* <li>Start and stop communication with the Niko Home Control System.
@ -46,7 +46,9 @@ public abstract class NikoHomeControlCommunication {
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 Map<String, NhcMeter> meters = new ConcurrentHashMap<>();
protected final Map<String, NhcAccess> accessDevices = new ConcurrentHashMap<>();
protected final Map<String, NhcVideo> videoDevices = new ConcurrentHashMap<>();
protected final NhcControllerEvent handler;
@ -97,8 +99,6 @@ public abstract class NikoHomeControlCommunication {
public synchronized void restartCommunication() {
resetCommunication();
logger.debug("restart communication from thread {}", Thread.currentThread().getId());
startCommunication();
}
@ -166,12 +166,30 @@ public abstract class NikoHomeControlCommunication {
}
/**
* Return all energyMeters meters in the Niko Home Control Controller.
* Return all meters in the Niko Home Control Controller.
*
* @return <code>Map&lt;String, {@link NhcEnergyMeter}></code>
* @return <code>Map&ltString, {@link NhcMeter}></code>
*/
public Map<String, NhcEnergyMeter> getEnergyMeters() {
return energyMeters;
public Map<String, NhcMeter> getMeters() {
return meters;
}
/**
* Return all access devices in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcAccess}></code>
*/
public Map<String, NhcAccess> getAccessDevices() {
return accessDevices;
}
/**
* Return all video devices in the Niko Home Control Controller.
*
* @return <code>Map&ltString, {@link NhcVideo}></code>
*/
public Map<String, NhcVideo> getVideoDevices() {
return videoDevices;
}
/**
@ -200,16 +218,113 @@ public abstract class NikoHomeControlCommunication {
public abstract void executeThermostat(String thermostatId, int overruleTemp, int overruleTime);
/**
* Start retrieving energy meter data from Niko Home Control.
* Query meter for energy, gas consumption or water production/consumption data. The query will update the total
* production/consumption and production/consumption from the start of the day through the meterReadingEvent
* callback in {@link NhcMeterEvent}.
*
* @param meterId
*/
public void startEnergyMeter(String energyMeterId) {
public abstract void executeMeter(String meterId);
/**
* Start retrieving energy meter data from Niko Home Control. The method is used to regularly retrigger the
* information flow. It can be left empty in concrete classes if the power data is flowing continuously.
*
* @param meterId
*/
public void startMeterLive(String meterId) {
NhcMeter meter = getMeters().get(meterId);
if (meter != null) {
meter.startMeterLive();
}
}
/**
* Stop retrieving energy meter data from Niko Home Control.
* Retrigger retrieving energy meter data from Niko Home Control. This is used if the power data does not continue
* flowing automatically and needs to be retriggered at regular intervals.
*
* @param meterId
*/
public void stopEnergyMeter(String energyMeterId) {
public abstract void retriggerMeterLive(String meterId);
/**
* Stop retrieving energy meter data from Niko Home Control. This method can be used to stop a scheduled retrigger
* of the information flow, as scheduled in {{@link #startMeterLive(String)}.
*
* @param meterId
*/
public void stopMeterLive(String meterId) {
NhcMeter meter = getMeters().get(meterId);
if (meter != null) {
meter.stopMeterLive();
}
};
/**
* Start retrieving meter data from Niko Home Control at a regular interval.
*
* @param meterId
* @param refresh reading frequency in minutes
*/
public void startMeter(String meterId, int refresh) {
NhcMeter meter = getMeters().get(meterId);
if (meter != null) {
meter.startMeter(refresh);
}
}
/**
* Stop retrieving meter data from Niko Home Control at a regular interval.
*/
public void stopMeter(String meterId) {
NhcMeter meter = getMeters().get(meterId);
if (meter != null) {
meter.stopMeter();
}
}
/**
* Stop retrieving meter data from Niko Home Control at a regular interval for all meters.
*/
public void stopAllMeters() {
for (String meterId : getMeters().keySet()) {
stopMeter(meterId);
stopMeterLive(meterId);
}
}
/**
* Execute a bell command on an access control device by sending it to Niko Home Control.
*
* @param accessId
*/
public void executeAccessBell(String accessId) {
}
/**
* Execute a bell command on video control device by sending it to Niko Home Control.
*
* @param accessId
* @param buttonIndex
*/
public void executeVideoBell(String accessId, int buttonIndex) {
}
/**
* Switches state ring and come on access control device (turns on if off and off if on) by sending it to Niko Home
* Control.
*
* @param accessId
* @param ringAndComeIn status
*/
public void executeAccessRingAndComeIn(String accessId, boolean ringAndComeIn) {
}
/**
* Execute an unlock command on an access control device by sending it to Niko Home Control.
*
* @param accessId
*/
public void executeAccessUnlock(String accessId) {
}
}

View File

@ -31,6 +31,23 @@ public class NikoHomeControlConstants {
GENERIC
}
// Access control types abstracted from NhcI and NhcII access control types
public static enum AccessType {
BASE,
RINGANDCOMEIN,
BELLBUTTON,
GENERIC
}
// Meter types abstracted from NhcI and NhcII meter types
public static enum MeterType {
ENERGY_LIVE,
ENERGY,
GAS,
WATER,
GENERIC
}
// switch and dimmer constants in the Nhc layer
public static final String NHCON = "On";
public static final String NHCOFF = "Off";
@ -45,6 +62,13 @@ public class NikoHomeControlConstants {
public static final String NHCUP = "Up";
public static final String NHCSTOP = "Stop";
// doorlock, bell and video constants in the Nhc layer
public static final String NHCOPEN = "Open";
public static final String NHCCLOSED = "Closed";
public static final String NHCIDLE = "Idle";
public static final String NHCRINGING = "Ringing";
public static final String NHCACTIVE = "Active";
// NhcII thermostat modes
public static final String[] THERMOSTATMODES = { "Day", "Night", "Eco", "Off", "Cool", "Prog1", "Prog2", "Prog3" };
public static final String[] THERMOSTATDEMAND = { "Cooling", "None", "Heating" };

View File

@ -30,11 +30,11 @@ 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
* Class {@link NikoHomeControlDiscover} is used to get the Niko Home Control IP-interfaces 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 Niko Home Control IP-interfaces 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.
*
@ -50,7 +50,7 @@ public final class NikoHomeControlDiscover {
private final Logger logger = LoggerFactory.getLogger(NikoHomeControlDiscover.class);
private List<String> nhcBridgeIds = new ArrayList<>();
private Map<String, InetAddress> addr = new HashMap<>();
private Map<String, InetAddress> inetAdresses = new HashMap<>();
private Map<String, Boolean> isNhcII = new HashMap<>();
/**
@ -82,7 +82,7 @@ public final class NikoHomeControlDiscover {
if (isNhcController(packet)) {
String bridgeId = setNhcBridgeId(packet);
setIsNhcII(bridgeId, packet);
setAddr(bridgeId, packet);
InetAddress addr = setAddr(bridgeId, packet);
logger.debug("IP address is {}, unique ID is {}", addr, bridgeId);
}
}
@ -104,7 +104,7 @@ public final class NikoHomeControlDiscover {
* @return the addr, null if not in the list of discovered bridgeId's
*/
public @Nullable InetAddress getAddr(String bridgeId) {
return addr.get(bridgeId);
return inetAdresses.get(bridgeId);
}
/**
@ -160,13 +160,16 @@ public final class NikoHomeControlDiscover {
}
/**
* Sets the IP address retrieved from the packet response
* Sets the IP address retrieved from the packet response and keep it with bridgeId
*
* @param bridgeId
* @param packet
* @return address from packet response
*/
private void setAddr(String bridgeId, DatagramPacket packet) {
addr.put(bridgeId, packet.getAddress());
private InetAddress setAddr(String bridgeId, DatagramPacket packet) {
InetAddress address = packet.getAddress();
inetAdresses.put(bridgeId, address);
return address;
}
/**

View File

@ -43,7 +43,7 @@ public class NhcAction1 extends NhcAction {
void execute();
}
private ScheduledExecutorService scheduler;
private final ScheduledExecutorService scheduler;
private volatile @Nullable Action rollershutterTask;
private volatile @Nullable ScheduledFuture<?> rollershutterStopTask;

View File

@ -19,8 +19,7 @@ import org.eclipse.jdt.annotation.Nullable;
* 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>
* {@link NhcMessageList1}, {@link NhcMessageListMap1}, {@link NhcMessageCmd1}.
*
* @author Mark Herwege - Initial Contribution
*/

View File

@ -31,10 +31,17 @@ class NhcMessageCmd1 extends NhcMessageBase1 {
private @Nullable Integer value1;
private @Nullable Integer value2;
private @Nullable Integer value3;
// thermostat
private @Nullable Integer mode;
private @Nullable Integer overrule;
private @Nullable String overruletime;
// energy
private @Nullable Integer channel;
private @Nullable String start;
private @Nullable String end;
NhcMessageCmd1(String cmd) {
super.setCmd(cmd);
}
@ -69,4 +76,15 @@ class NhcMessageCmd1 extends NhcMessageBase1 {
this.overruletime = overruletime;
return this;
}
NhcMessageCmd1 withChannel(int channel) {
this.channel = channel;
return this;
}
NhcMessageCmd1 withInterval(String start, String end) {
this.start = start;
this.end = end;
return this;
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2024 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 org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class {@link NhcMessageList1} 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. Extends {@link NhcMessageBase1}.
* <p>
* Example: <code>{"cmd":"getenergydata","data":[ 0, 0, 12, 18, 7]}</code>
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
class NhcMessageList1 extends NhcMessageBase1 {
private List<String> data = new ArrayList<>();
List<String> getData() {
return data;
}
void setData(List<String> data) {
this.data = data;
}
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2024 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.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
/**
* The {@link NhcMeter1} class represents the meter Niko Home Control communication object. It contains all fields
* representing a Niko Home Control meter and has methods to receive meter information.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcMeter1 extends NhcMeter {
private static final Map<String, String> TYPE = Map.of("0", "Global", "1", "Submeasurement", "2", "Producer");
private final String meterType;
NhcMeter1(String id, String name, MeterType meterType, @Nullable String location, String type,
@Nullable LocalDateTime referenceDate, NikoHomeControlCommunication nhcComm,
ScheduledExecutorService scheduler) {
super(id, name, meterType, referenceDate, location, nhcComm, scheduler);
this.meterType = TYPE.getOrDefault(type, "");
}
/**
* @return type of meter: Global, Submeasurement or Producer
*/
public String getMeterType() {
return meterType;
}
}

View File

@ -20,20 +20,33 @@ import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
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.NhcMeter;
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.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -60,6 +73,11 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
private String eventThreadName = THREAD_NAME_PREFIX;
private static final int TIMEOUT_MILLIS = 2000;
private static final int TIMOUT_LONG_MILLIS = 10000;
private static final String DATE_TIME_PATTERN = "yyyyMMddHHmm";
private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
private final NhcSystemInfo1 systemInfo = new NhcSystemInfo1();
private final Map<String, NhcLocation1> locations = new ConcurrentHashMap<>();
@ -67,9 +85,25 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
private @Nullable PrintWriter nhcOut;
private @Nullable BufferedReader nhcIn;
private @Nullable Socket nhcEnergySocket; // dedicated socket for energy data to avoid blocking main communication
private @Nullable PrintWriter nhcEnergyOut;
private @Nullable BufferedReader nhcEnergyIn;
private volatile boolean listenerStopped;
private volatile boolean nhcEventsRunning;
// Synchronization of send/receive, used to block sending new commands when response to previous command has not
// been received
private volatile @Nullable CompletableFuture<Boolean> cmdResponseFuture;
private Object executeMeterLock = new Object(); // only allow a single send and read cycle at the same time
// The meter reading response does not contain the channel and end date, so we have to store it when giving a meter
// reading command
private volatile String meterReadingChannel = "";
private @Nullable volatile LocalDateTime meterReadingEnd;
private volatile boolean meterReadingInit;
// 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;
@ -135,7 +169,21 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
public synchronized void resetCommunication() {
listenerStopped = true;
Socket socket = nhcSocket;
socketClose(nhcSocket);
nhcSocket = null;
socketClose(nhcEnergySocket);
nhcEnergySocket = null;
CompletableFuture<Boolean> future = cmdResponseFuture;
if (future != null) {
future.complete(false);
}
cmdResponseFuture = null;
logger.debug("communication stopped");
}
private void socketClose(@Nullable Socket socket) {
if (socket != null) {
try {
socket.close();
@ -143,9 +191,6 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
// ignore IO Error when trying to close the socket if the intention is to close it anyway
}
}
nhcSocket = null;
logger.debug("communication stopped");
}
@Override
@ -197,54 +242,48 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
* 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");
}
private void sendAndReadMessage(String command) throws IOException {
Socket socket = nhcSocket;
PrintWriter out = nhcOut;
BufferedReader in = nhcIn;
if (in != null) {
sendMessage(new NhcMessageCmd1(command));
readMessage(in.readLine());
if ((socket != null) && (in != null) && (out != null)) {
sendAndReadMessage(new NhcMessageCmd1("systeminfo"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("listlocations"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("listactions"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("listthermostat"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("listthermostatHVAC"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("listenergy"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("readtariffdata"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("getalarms"), socket, out, in);
sendAndReadMessage(new NhcMessageCmd1("startevents"), socket, out, in);
} else {
throw (new IOException("socket not initialized"));
}
}
/**
* Called by other methods to send json cmd to Niko Home Control.
* Send a command and read the response. This should only be used when there is no listener active on the socket to
* listen to responses. In that case send and received would be decoupled.
*
* @param nhcMessage
* @param command
* @param out
* @param in
* @throws IOException
*/
private synchronized void sendMessage(Object nhcMessage) {
String json = gsonOut.toJson(nhcMessage);
PrintWriter out = nhcOut;
if (out != null) {
logger.debug("send json {}", json);
out.println(json);
if (out.checkError()) {
logger.debug("error sending message, trying to restart communication");
restartCommunication();
// retry sending after restart
logger.debug("resend json {}", json);
out.println(json);
if (out.checkError()) {
handler.controllerOffline("@text/offline.communication-error");
// Keep on trying to restart, but don't send message anymore
scheduleRestartCommunication();
}
}
private void sendAndReadMessage(Object command, Socket socket, PrintWriter out, BufferedReader in)
throws IOException {
socket.setSoTimeout(TIMOUT_LONG_MILLIS);
try {
sendMessage(command, out, false);
readMessage(in.readLine(), false);
} catch (SocketTimeoutException e) {
logger.debug("Did not receive a response in {} ms", TIMOUT_LONG_MILLIS);
throw (e);
}
socket.setSoTimeout(0);
}
/**
@ -253,42 +292,162 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
* @param nhcMessage message read from Niko Home Control.
*/
private void readMessage(@Nullable String nhcMessage) {
logger.debug("received json {}", nhcMessage);
readMessage(nhcMessage, true);
}
private void readMessage(@Nullable String nhcMessage, boolean hasEventLoop) {
NhcMessageBase1 nhcMessageGson;
String cmd;
String event;
if (nhcMessage == null) {
logger.debug("empty message, nothing to interpret");
return;
}
try {
NhcMessageBase1 nhcMessageGson = gsonIn.fromJson(nhcMessage, NhcMessageBase1.class);
nhcMessageGson = gsonIn.fromJson(nhcMessage, NhcMessageBase1.class);
if (nhcMessageGson == null) {
return;
}
String cmd = nhcMessageGson.getCmd();
String event = nhcMessageGson.getEvent();
cmd = nhcMessageGson.getCmd();
event = nhcMessageGson.getEvent();
} catch (JsonParseException e) {
logger.debug("not acted on unsupported json {}", nhcMessage);
return;
}
logger.trace("received json {}", nhcMessage);
// We received a command response from the listener, so can allow the next cmd to be sent
if (hasEventLoop && !cmd.isEmpty()) {
CompletableFuture<Boolean> responseFuture = cmdResponseFuture;
if (responseFuture != null) {
responseFuture.complete(true);
}
cmdResponseFuture = null;
}
try {
if ("systeminfo".equals(cmd)) {
cmdSystemInfo(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("startevents".equals(cmd)) {
cmdStartEvents(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("stoplive".equals(cmd)) {
cmdStopLive(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("getlive".equals(cmd)) {
cmdGetLive(((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 ("listenergy".equals(cmd)) {
cmdListEnergy(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("executeactions".equals(cmd)) {
cmdExecuteActions(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("executethermostat".equals(cmd)) {
cmdExecuteThermostat(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("getenergydata".equals(cmd)) {
cmdGetEnergyData(((NhcMessageList1) nhcMessageGson).getData(), meterReadingChannel, meterReadingEnd,
meterReadingInit);
} else if ("listactions".equals(event)) {
eventListActions(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("listthermostat".equals(event)) {
eventListThermostat(((NhcMessageListMap1) nhcMessageGson).getData());
} else if ("getlive".equals(event)) {
eventGetLive(((NhcMessageMap1) nhcMessageGson).getData());
} else if ("getalarms".equals(event)) {
eventGetAlarms(((NhcMessageMap1) nhcMessageGson).getData());
} else {
logger.debug("not acted on json {}", nhcMessage);
}
} catch (JsonParseException e) {
logger.debug("not acted on unsupported json {}", nhcMessage);
} catch (ClassCastException e) {
readError(nhcMessage, nhcMessageGson);
}
}
private void readError(String nhcMessage, NhcMessageBase1 nhcMessageGson) {
try {
Map<String, String> data = ((NhcMessageMap1) nhcMessageGson).getData();
if (data.containsKey("error")) {
logger.warn("received error message {} from controller", data.get("error"));
logger.debug("received error with json {}", nhcMessage);
} else {
logger.debug("received unsupported format in json {}", nhcMessage);
}
} catch (ClassCastException e) {
logger.debug("received unsupported format in json {}", nhcMessage);
}
}
private void sendMessage(Object nhcMessage, PrintWriter out) {
sendMessage(nhcMessage, out, true);
}
/**
* Called by other methods to send json cmd to Niko Home Control.
*
* @param nhcMessage
* @param out
* @param hasEventLoop Should be true for asynchronous send/receive
*/
private synchronized void sendMessage(Object nhcMessage, PrintWriter out, boolean hasEventLoop) {
String json = gsonOut.toJson(nhcMessage);
logger.trace("request to send json {}", json);
boolean responseReceived = true;
boolean sendFailure = false;
// When the event loop is running, we need to make sure we received the previous response before sending
// anything
if (hasEventLoop) {
CompletableFuture<Boolean> responseFuture = cmdResponseFuture;
if ((responseFuture != null) && !responseFuture.isDone()) {
try {
// Wait until we have received the full response to the previous command.
responseFuture.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
responseReceived = false;
sendFailure = true;
logger.debug("exception waiting cmd response");
}
}
cmdResponseFuture = new CompletableFuture<>();
}
if (responseReceived) {
logger.debug("send json {}", json);
out.println(json);
if (out.checkError()) {
sendFailure = true;
}
}
if (sendFailure) {
// This will make sure no further request are being sent until we are back up
nhcEnergyOut = null;
nhcOut = null;
restartOnSendFailure(out, json);
}
}
private void restartOnSendFailure(PrintWriter out, String json) {
logger.debug("error sending message, trying to restart communication");
restartCommunication();
// retry sending after restart
cmdResponseFuture = new CompletableFuture<>();
logger.debug("resend json {}", json);
out.println(json);
if (out.checkError()) {
// This will make sure no further request are being sent until we are back up
nhcOut = null;
handler.controllerOffline("@text/offline.communication-error");
// Keep on trying to restart, but don't send message anymore
scheduleRestartCommunication();
}
}
@ -299,7 +458,18 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private synchronized void cmdSystemInfo(Map<String, String> data) {
private int parseIntOrThrow(@Nullable String str) throws IllegalArgumentException {
if (str == null) {
throw new IllegalArgumentException("String is null");
}
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e);
}
}
private void cmdSystemInfo(Map<String, String> data) {
logger.debug("systeminfo");
setIfPresent(data, "swversion", systemInfo::setSwVersion);
@ -319,7 +489,7 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
*
* @return the systemInfo
*/
public synchronized NhcSystemInfo1 getSystemInfo() {
public NhcSystemInfo1 getSystemInfo() {
return systemInfo;
}
@ -337,6 +507,24 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private void cmdStopLive(Map<String, String> data) {
String errorCodeString = data.get("error");
if (errorCodeString != null) {
int errorCode = Integer.parseInt(errorCodeString);
if (errorCode == 0) {
logger.debug("Stop live meter events success");
} else {
logger.debug("error code {} returned on stop live", errorCode);
}
} else {
logger.debug("could not determine error code returned on stop live");
}
}
private void cmdGetLive(Map<String, String> data) {
eventGetLive(data);
}
private void cmdListLocations(List<Map<String, String>> data) {
logger.debug("list locations");
@ -400,7 +588,8 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
if (locationId != null && !locationId.isEmpty()) {
location = locations.getOrDefault(locationId, new NhcLocation1("")).getName();
}
if (!actions.containsKey(id)) {
if (!getActions().containsKey(id)) {
// Initial instantiation of NhcAction class for action object
NhcAction nhcAction = new NhcAction1(id, name, actionType, location, this, scheduler);
if (actionType == ActionType.ROLLERSHUTTER) {
@ -412,7 +601,7 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
// Action object already exists, so only update state, name and location.
// If we would re-instantiate action, we would lose pointer back from action to thing handler that was
// set in thing handler initialize().
NhcAction nhcAction = actions.get(id);
NhcAction nhcAction = getActions().get(id);
if (nhcAction != null) {
nhcAction.setName(name);
nhcAction.setLocation(location);
@ -422,17 +611,6 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private int parseIntOrThrow(@Nullable String str) throws IllegalArgumentException {
if (str == null) {
throw new IllegalArgumentException("String is null");
}
try {
return Integer.parseInt(str);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e);
}
}
private void cmdListThermostat(List<Map<String, String>> data) {
logger.debug("list thermostats");
@ -467,7 +645,8 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
nhcLocation = locations.get(locationId);
}
String location = (nhcLocation != null) ? nhcLocation.getName() : null;
NhcThermostat t = thermostats.computeIfAbsent(id, i -> {
NhcThermostat t = getThermostats().computeIfAbsent(id, i -> {
// Initial instantiation of NhcThermostat class for thermostat object
if (name != null) {
return new NhcThermostat1(i, name, location, this);
@ -479,7 +658,7 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
t.setName(name);
}
t.setLocation(location);
t.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
t.setState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
} catch (IllegalArgumentException e) {
// do nothing
@ -487,6 +666,77 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private void cmdListEnergy(List<Map<String, String>> data) {
logger.debug("list energy");
DateTimeFormatter format = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN).withZone(getTimeZone());
for (Map<String, String> meter : data) {
String id = meter.get("channel");
if (id == null) {
logger.debug("skipping energy meter {}, id not found", meter);
continue;
}
String name = meter.get("name");
if (name == null) {
logger.debug("name not found in meter {}", meter);
continue;
}
String type = meter.getOrDefault("type", "");
String energy = meter.getOrDefault("energy", "");
String live = meter.getOrDefault("live", "");
String referenceDateString = meter.getOrDefault("startdate", "");
LocalDateTime referenceDate;
try {
referenceDate = LocalDateTime.parse(referenceDateString, format);
} catch (DateTimeParseException e) {
logger.debug("cannot parse reference date {} for meter {}", referenceDateString, id);
referenceDate = null;
}
MeterType meterType = MeterType.GENERIC;
switch (energy) {
case "0":
if ("1".equals(live)) {
meterType = MeterType.ENERGY_LIVE;
} else {
meterType = MeterType.ENERGY;
}
break;
case "1":
meterType = MeterType.GAS;
break;
case "2":
meterType = MeterType.WATER;
break;
default:
logger.debug("unknown meter energy {} for meter {}", energy, id);
continue;
}
String locationId = meter.get("location");
NhcLocation1 nhcLocation = null;
if (!((locationId == null) || locationId.isEmpty())) {
nhcLocation = locations.get(locationId);
}
String location = (nhcLocation != null) ? nhcLocation.getName() : null;
if (!getMeters().containsKey(id)) {
// Initial instantiation of NhcMeter class for meter object
NhcMeter nhcMeter = new NhcMeter1(id, name, meterType, location, type, referenceDate, this, scheduler);
meters.put(id, nhcMeter);
} else {
// Meter object already exists, so only name and location.
// If we would re-instantiate meter, we would lose pointer back from meter to thing handler that was
// set in thing handler initialize().
NhcMeter nhcMeter = getMeters().get(id);
if (nhcMeter != null) {
nhcMeter.setName(name);
nhcMeter.setLocation(location);
}
}
}
}
private void cmdExecuteActions(Map<String, String> data) {
try {
int errorCode = parseIntOrThrow(data.get("error"));
@ -513,10 +763,70 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private void cmdGetEnergyData(List<String> data, String id, @Nullable LocalDateTime meterReadingEnd, boolean init) {
if (id.isEmpty()) {
logger.debug("received meter data but none requested, ignoring");
return;
}
NhcMeter meter = getMeters().get(id);
if (meter == null) {
logger.debug("received meter data for {} but no meter found, ignoring", id);
return;
}
if (meterReadingEnd == null) {
logger.debug("received meter reading data for {} but request did not have end date, ignoring", id);
return;
}
LocalDateTime lastReading = meter.getLastReading();
if (lastReading == null) {
lastReading = meter.getReferenceDate();
if (lastReading == null) {
logger.debug("error getting meter data for {}, no meter reference date available", id);
return;
}
}
int reading;
int dayReading;
ZonedDateTime lastReadingCurrentZone = lastReading.atZone(ZoneOffset.UTC).withZoneSameInstant(getTimeZone());
ZonedDateTime meterReadingEndCurrentZone = meterReadingEnd.atZone(ZoneOffset.UTC)
.withZoneSameInstant(getTimeZone());
boolean dayChange = meterReadingEndCurrentZone.truncatedTo(ChronoUnit.DAYS).isAfter(lastReadingCurrentZone);
long beforeDayStart = Math.max(0, ChronoUnit.MINUTES.between(lastReadingCurrentZone,
meterReadingEndCurrentZone.truncatedTo(ChronoUnit.DAYS)) / 10);
logger.trace("received {} individual meter readings for {}, summing up", data.size(), id);
try {
if (init) {
reading = data.stream().mapToInt(Integer::parseInt).sum();
dayReading = data.stream().skip(beforeDayStart).mapToInt(Integer::parseInt).sum();
} else {
int value = data.stream().skip(1).mapToInt(Integer::parseInt).sum();
reading = meter.getReadingInt() + value;
logger.trace("adding {} to meter {} reading, new reading {}", value, id, reading);
if (dayChange) {
dayReading = data.stream().skip(1 + beforeDayStart).mapToInt(Integer::parseInt).sum();
logger.trace("meter {} day reading, it's a new day, new reading {}", id, dayReading);
} else {
dayReading = meter.getDayReadingInt() + value;
logger.trace("adding {} to meter {} day reading, new reading {}", value, id, dayReading);
}
}
} catch (NumberFormatException e) {
logger.debug("error in meter readings received for {}", id);
return;
}
logger.debug("received meter reading for {}: total {}, day {}", id, reading, dayReading);
meter.setReading(reading, dayReading, meterReadingEnd);
}
private void eventListActions(List<Map<String, String>> data) {
for (Map<String, String> action : data) {
String id = action.get("id");
if (id == null || !actions.containsKey(id)) {
if (id == null || !getActions().containsKey(id)) {
logger.warn("action in controller not known {}", id);
return;
}
@ -524,7 +834,7 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
if (stateString != null) {
int state = Integer.parseInt(stateString);
logger.debug("event execute action {} with state {}", id, state);
NhcAction action1 = actions.get(id);
NhcAction action1 = getActions().get(id);
if (action1 != null) {
action1.setState(state);
}
@ -536,7 +846,7 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
for (Map<String, String> thermostat : data) {
try {
String id = thermostat.get("id");
if (!thermostats.containsKey(id)) {
if (!getThermostats().containsKey(id)) {
logger.warn("thermostat in controller not known {}", id);
return;
}
@ -557,11 +867,11 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
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 {}",
"event execute thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
id, measured, setpoint, mode, overrule, overruletime, ecosave, demand);
NhcThermostat t = thermostats.get(id);
NhcThermostat t = getThermostats().get(id);
if (t != null) {
t.updateState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
t.setState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
} catch (IllegalArgumentException e) {
// do nothing
@ -569,6 +879,20 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
}
}
private void eventGetLive(Map<String, String> data) {
try {
String channel = data.get("channel");
int v = parseIntOrThrow(data.get("v"));
logger.debug("event live power channel {} with v {}", channel, v);
NhcMeter e = getMeters().get(channel);
if (e != null) {
e.setPower(v);
}
} catch (IllegalArgumentException e) {
// do nothing
}
}
private void eventGetAlarms(Map<String, String> data) {
String alarmText = data.get("text");
if (alarmText == null) {
@ -591,23 +915,124 @@ public class NikoHomeControlCommunication1 extends NikoHomeControlCommunication
@Override
public void executeAction(String actionId, String value) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executeactions", Integer.parseInt(actionId),
Integer.parseInt(value));
sendMessage(nhcCmd);
PrintWriter out = nhcOut;
if (out != null) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executeactions", Integer.parseInt(actionId),
Integer.parseInt(value));
sendMessage(nhcCmd, out);
}
}
@Override
public void executeThermostat(String thermostatId, String mode) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withMode(Integer.parseInt(mode));
sendMessage(nhcCmd);
PrintWriter out = nhcOut;
if (out != null) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("executethermostat", Integer.parseInt(thermostatId))
.withMode(Integer.parseInt(mode));
sendMessage(nhcCmd, out);
}
}
@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);
PrintWriter out = nhcOut;
if (out != null) {
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, out);
}
}
@Override
public void executeMeter(String meterId) {
NhcMeter meter = getMeters().get(meterId);
if (meter == null) {
return;
}
if (!communicationActive()) {
logger.debug("Communication not active, not getting meter data for {}", meterId);
return;
}
// only update one meter at a time and do it on a dedicated socket
synchronized (executeMeterLock) {
Socket socket = nhcEnergySocket;
BufferedReader in = nhcEnergyIn;
PrintWriter out = nhcEnergyOut;
try {
if ((socket == null) || socket.isClosed() || (in == null) || (out == null)) {
InetAddress addr = handler.getAddr();
int port = handler.getPort();
socketClose(socket);
socket = new Socket(addr, port);
nhcEnergySocket = socket;
out = new PrintWriter(socket.getOutputStream(), true);
nhcEnergyOut = out;
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
nhcEnergyIn = in;
logger.debug("connected via local port {} for energy data", socket.getLocalPort());
}
} catch (IOException e) {
logger.debug("not able to establish communication to read meter data");
return;
}
try {
meterReadingInit = false;
LocalDateTime start = meter.getLastReading();
if (start == null) {
meterReadingInit = true;
start = meter.getReferenceDate();
if (start == null) {
logger.debug("error getting meter data, no meter reference date available");
return;
}
}
String readingStart = start.format(DATE_TIME_FORMAT);
LocalDateTime end = ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC).toLocalDateTime();
String readingEnd = end.format(DATE_TIME_FORMAT);
meterReadingChannel = meterId;
meterReadingEnd = end;
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("getenergydata").withChannel(Integer.parseInt(meterId))
.withInterval(readingStart, readingEnd);
if (logger.isTraceEnabled()) {
long interval = ChronoUnit.MINUTES.between(start, end);
long number = interval / 10;
int minute = start.getMinute();
if ((number > 0) || (((minute + interval) / 10) > (start.getMinute() / 10))) {
number += 1;
}
logger.trace("expecting {} readings between {} and {} for {}", number, readingStart, readingEnd,
meterId);
}
sendAndReadMessage(nhcCmd, socket, out, in);
} catch (IOException e) {
logger.debug("error getting meter data for {}", meterId);
}
}
}
@Override
public synchronized void retriggerMeterLive(String meterId) {
if (!communicationActive()) {
logger.debug("Communication not active, not live getting meter data for {}", meterId);
return;
}
PrintWriter out = nhcOut;
if (out != null) {
NhcMessageCmd1 nhcCmd = new NhcMessageCmd1("stoplive").withChannel(Integer.parseInt(meterId));
sendMessage(nhcCmd, out);
nhcCmd = new NhcMessageCmd1("getlive").withChannel(Integer.parseInt(meterId));
sendMessage(nhcCmd, out);
}
}
}

View File

@ -70,23 +70,33 @@ class NikoHomeControlMessageDeserializer1 implements JsonDeserializer<NhcMessage
data.put(entry.getKey(), entry.getValue().getAsString());
}
((NhcMessageMap1) message).setData(data);
} else if (jsonData.isJsonArray()) {
JsonArray jsonDataArray = jsonData.getAsJsonArray();
message = new NhcMessageListMap1();
// check if this is an array of primitives or objects
if ((jsonDataArray.size() > 0) && jsonDataArray.get(0).isJsonPrimitive()) {
message = new NhcMessageList1();
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());
List<String> dataList = new ArrayList<>();
for (JsonElement jsonElement : jsonDataArray) {
dataList.add(jsonElement.getAsJsonPrimitive().getAsString());
}
dataList.add(data);
((NhcMessageList1) message).setData(dataList);
} else {
message = new NhcMessageListMap1();
List<Map<String, String>> dataList = new ArrayList<>();
for (JsonElement jsonElement : jsonDataArray) {
JsonObject jsonDataObject = jsonElement.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);
}
((NhcMessageListMap1) message).setData(dataList);
}
}

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2024 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.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.AccessType;
/**
* The {@link NhcAccess2} class represents the access control Niko Home Control communication object. It contains all
* fields representing a Niko Home Control access control device and has methods to unlock the door in Niko Home Control
* and receive bell signals.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcAccess2 extends NhcAccess {
private final String deviceType;
private final String deviceTechnology;
private final String deviceModel;
NhcAccess2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
@Nullable String location, AccessType accessType, @Nullable String buttonId,
NikoHomeControlCommunication nhcComm) {
super(id, name, location, accessType, buttonId, nhcComm);
this.deviceType = deviceType;
this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
}
/**
* @return type as returned from Niko Home Control
*/
public String getDeviceType() {
return deviceType;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getDeviceTechnology() {
return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
}
}

View File

@ -35,9 +35,9 @@ public class NhcAction2 extends NhcAction {
private final Logger logger = LoggerFactory.getLogger(NhcAction2.class);
private volatile boolean booleanState;
private String deviceType;
private String deviceTechnology;
private String deviceModel;
private final String deviceType;
private final String deviceTechnology;
private final String deviceModel;
NhcAction2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
@Nullable String location, ActionType type, NikoHomeControlCommunication nhcComm) {

View File

@ -92,12 +92,23 @@ class NhcDevice2 {
// fields for access control
@Nullable
String doorlock;
// fields for video devices
@Nullable
String ipAddress;
@Nullable
String callStatus01;
@Nullable
String callStatus02;
@Nullable
String callStatus03;
@Nullable
String callStatus04;
}
static class NhcTrait {
@Nullable
String macAddress;
// fields for energyMeters metering
// fields for metering
@Nullable
String channel;
@Nullable
@ -120,6 +131,20 @@ class NhcDevice2 {
String clampType;
@Nullable
String shortName;
// fields for access control
@Nullable
String buttonId;
@Nullable
String ringTone;
@Nullable
String declineCallAppliedOnAllDevices;
@Nullable
String iconCode;
// fields for video devices
@Nullable
String mjpegUri;
@Nullable
String tnUri;
}
String name = "";

View File

@ -1,99 +0,0 @@
/**
* Copyright (c) 2010-2024 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 deviceType;
private String deviceTechnology;
private String deviceModel;
protected NhcEnergyMeter2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
@Nullable String location, NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
super(id, name, location, nhcComm);
this.deviceType = deviceType;
this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
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 type as returned from Niko Home Control
*/
public String getDeviceType() {
return deviceType;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getDeviceTechnology() {
return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
}
}

View File

@ -0,0 +1,66 @@
/**
* Copyright (c) 2010-2024 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.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
/**
* The {@link NhcMeter2} class represents the meter Niko Home Control communication object. It contains all fields
* representing a Niko Home Control meter and has methods to receive meter usage information.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcMeter2 extends NhcMeter {
private final String deviceType;
private final String deviceTechnology;
private final String deviceModel;
protected NhcMeter2(String id, String name, MeterType meterType, String deviceType, String deviceTechnology,
String deviceModel, @Nullable LocalDateTime referenceDate, @Nullable String location,
NikoHomeControlCommunication nhcComm, ScheduledExecutorService scheduler) {
super(id, name, meterType, referenceDate, location, nhcComm, scheduler);
this.deviceType = deviceType;
this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
}
/**
* @return type as returned from Niko Home Control
*/
public String getDeviceType() {
return deviceType;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getDeviceTechnology() {
return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
}
}

View File

@ -59,8 +59,8 @@ public class NhcMqttConnection2 implements MqttActionCallback {
private volatile @Nullable CompletableFuture<Boolean> subscribedFuture;
private volatile @Nullable CompletableFuture<Boolean> stoppedFuture;
private MqttMessageSubscriber messageSubscriber;
private MqttConnectionObserver connectionObserver;
private final MqttMessageSubscriber messageSubscriber;
private final MqttConnectionObserver connectionObserver;
private TrustManager[] trustManagers;
private String clientId;

View File

@ -33,9 +33,9 @@ public class NhcThermostat2 extends NhcThermostat {
private final Logger logger = LoggerFactory.getLogger(NhcThermostat2.class);
private String deviceType;
private String deviceTechnology;
private String deviceModel;
private final String deviceType;
private final String deviceTechnology;
private final String deviceModel;
protected NhcThermostat2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
@Nullable String location, NikoHomeControlCommunication nhcComm) {

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2024 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.NHCRINGING;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcVideo;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link NhcVideo2} class represents a Niko Home Control II video door station device. It is used in conjunction
* with NhcAccess2 to capture the bell signal on a video door station for access control.
*
* @author Mark Herwege - Initial Contribution
*/
@NonNullByDefault
public class NhcVideo2 extends NhcVideo {
private final Logger logger = LoggerFactory.getLogger(NhcVideo2.class);
private final String deviceType;
private final String deviceTechnology;
private final String deviceModel;
NhcVideo2(String id, String name, String deviceType, String deviceTechnology, String deviceModel,
@Nullable String macAddress, @Nullable String ipAddress, @Nullable String mjpegUri, @Nullable String tnUri,
NikoHomeControlCommunication nhcComm) {
super(id, name, macAddress, ipAddress, mjpegUri, tnUri, "robinsip".equals(deviceModel), nhcComm);
this.deviceType = deviceType;
this.deviceTechnology = deviceTechnology;
this.deviceModel = deviceModel;
}
/**
* @return type as returned from Niko Home Control
*/
public String getDeviceType() {
return deviceType;
}
/**
* @return technology as returned from Niko Home Control
*/
public String getDeviceTechnology() {
return deviceTechnology;
}
/**
* @return model as returned from Niko Home Control
*/
public String getDeviceModel() {
return deviceModel;
}
@Override
public void updateState(int buttonIndex, @Nullable String status) {
callStatus.compute(buttonIndex, (k, v) -> status);
NhcAccess access = nhcAccessMap.get(buttonIndex);
if (access != null) {
logger.trace("updating bell state for button {} linked to access id {}", buttonIndex, access.getId());
access.updateBellState(NHCRINGING.equals(status) ? true : false);
}
}
}

View File

@ -33,14 +33,19 @@ import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAccess;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcAction;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcControllerEvent;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcEnergyMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcMeter;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcThermostat;
import org.openhab.binding.nikohomecontrol.internal.protocol.NhcVideo;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlCommunication;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.AccessType;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.ActionType;
import org.openhab.binding.nikohomecontrol.internal.protocol.NikoHomeControlConstants.MeterType;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcParameter;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcProperty;
import org.openhab.binding.nikohomecontrol.internal.protocol.nhc2.NhcDevice2.NhcTrait;
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;
@ -156,7 +161,7 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
// 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("exception waiting for connection start");
logger.debug("exception waiting for connection start: {}", e.toString());
return false;
}
}
@ -372,94 +377,214 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
location = parameters.stream().map(p -> p.locationName).filter(Objects::nonNull).findFirst().orElse(null);
}
if ("action".equals(device.type) || "virtual".equals(device.type)) {
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":
case "flag":
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("device type {} and model {} not recognised for {}, {}, ignoring", device.type,
device.model, device.uuid, device.name);
return;
}
NhcAction nhcAction = actions.get(device.uuid);
if (nhcAction != null) {
// update name and location so discovery will see updated name and location
nhcAction.setName(device.name);
nhcAction.setLocation(location);
} else {
logger.debug("adding action device {} model {}, {}", device.uuid, device.model, device.name);
nhcAction = new NhcAction2(device.uuid, device.name, device.type, device.technology, device.model,
location, actionType, this);
}
actions.put(device.uuid, nhcAction);
if ("videodoorstation".equals(device.type) || "vds".equals(device.type)) {
addVideoDevice(device);
} else if ("accesscontrol".equals(device.model) || "bellbutton".equals(device.model)) {
addAccessDevice(device, location);
} else if ("action".equals(device.type) || "virtual".equals(device.type)) {
addActionDevice(device, location);
} else if ("thermostat".equals(device.type)) {
NhcThermostat nhcThermostat = thermostats.get(device.uuid);
if (nhcThermostat != null) {
nhcThermostat.setName(device.name);
nhcThermostat.setLocation(location);
} else {
logger.debug("adding thermostat device {} model {}, {}", device.uuid, device.model, device.name);
nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.type, device.technology,
device.model, location, this);
}
thermostats.put(device.uuid, nhcThermostat);
addThermostatDevice(device, location);
} else if ("centralmeter".equals(device.type) || "energyhome".equals(device.type)) {
NhcEnergyMeter nhcEnergyMeter = energyMeters.get(device.uuid);
if (nhcEnergyMeter != null) {
nhcEnergyMeter.setName(device.name);
nhcEnergyMeter.setLocation(location);
} else {
logger.debug("adding energy meter device {} model {}, {}", device.uuid, device.model, device.name);
nhcEnergyMeter = new NhcEnergyMeter2(device.uuid, device.name, device.type, device.technology,
device.model, location, this, scheduler);
}
energyMeters.put(device.uuid, nhcEnergyMeter);
addMeterDevice(device, location);
} else {
logger.debug("device type {} and model {} not supported for {}, {}", device.type, device.model, device.uuid,
device.name);
}
}
private void addActionDevice(NhcDevice2 device, @Nullable String location) {
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":
case "flag":
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("device type {} and model {} not recognised for {}, {}, ignoring", device.type,
device.model, device.uuid, device.name);
return;
}
NhcAction nhcAction = actions.get(device.uuid);
if (nhcAction != null) {
// update name and location so discovery will see updated name and location
nhcAction.setName(device.name);
nhcAction.setLocation(location);
} else {
logger.debug("adding action device {} model {}, {}", device.uuid, device.model, device.name);
nhcAction = new NhcAction2(device.uuid, device.name, device.type, device.technology, device.model, location,
actionType, this);
}
actions.put(device.uuid, nhcAction);
}
private void addThermostatDevice(NhcDevice2 device, @Nullable String location) {
NhcThermostat nhcThermostat = thermostats.get(device.uuid);
if (nhcThermostat != null) {
nhcThermostat.setName(device.name);
nhcThermostat.setLocation(location);
} else {
logger.debug("adding thermostat device {} model {}, {}", device.uuid, device.model, device.name);
nhcThermostat = new NhcThermostat2(device.uuid, device.name, device.type, device.technology, device.model,
location, this);
}
thermostats.put(device.uuid, nhcThermostat);
}
private void addMeterDevice(NhcDevice2 device, @Nullable String location) {
NhcMeter nhcMeter = meters.get(device.uuid);
if (nhcMeter != null) {
nhcMeter.setName(device.name);
nhcMeter.setLocation(location);
} else {
logger.debug("adding energy meter device {} model {}, {}", device.uuid, device.model, device.name);
nhcMeter = new NhcMeter2(device.uuid, device.name, MeterType.ENERGY_LIVE, device.type, device.technology,
device.model, null, location, this, scheduler);
}
meters.put(device.uuid, nhcMeter);
}
private void addAccessDevice(NhcDevice2 device, @Nullable String location) {
AccessType accessType = AccessType.BASE;
if ("bellbutton".equals(device.model)) {
accessType = AccessType.BELLBUTTON;
} else {
List<NhcProperty> properties = device.properties;
if (properties != null) {
boolean hasBasicState = properties.stream().anyMatch(p -> (p.basicState != null));
if (hasBasicState) {
accessType = AccessType.RINGANDCOMEIN;
}
}
}
NhcAccess2 nhcAccess = (NhcAccess2) accessDevices.get(device.uuid);
if (nhcAccess != null) {
nhcAccess.setName(device.name);
nhcAccess.setLocation(location);
} else {
String buttonId = null;
List<NhcParameter> parameters = device.parameters;
if (parameters != null) {
buttonId = parameters.stream().map(p -> p.buttonId).filter(Objects::nonNull).findFirst().orElse(null);
}
logger.debug("adding access device {} model {} type {}, {}", device.uuid, device.model, accessType,
device.name);
nhcAccess = new NhcAccess2(device.uuid, device.name, device.type, device.technology, device.model, location,
accessType, buttonId, this);
if (buttonId != null) {
NhcAccess2 access = nhcAccess;
String macAddress = buttonId.split("_")[0];
videoDevices.forEach((key, videoDevice) -> {
if (macAddress.equals(videoDevice.getMacAddress())) {
int buttonIndex = access.getButtonIndex();
logger.debug("link access device {} to video device {} button {}", device.uuid,
videoDevice.getId(), buttonIndex);
videoDevice.setNhcAccess(buttonIndex, access);
access.setNhcVideo(videoDevice);
}
});
}
}
accessDevices.put(device.uuid, nhcAccess);
}
private void addVideoDevice(NhcDevice2 device) {
NhcVideo2 nhcVideo = (NhcVideo2) videoDevices.get(device.uuid);
if (nhcVideo != null) {
nhcVideo.setName(device.name);
} else {
String macAddress = null;
String ipAddress = null;
String mjpegUri = null;
String tnUri = null;
List<NhcTrait> traits = device.traits;
if (traits != null) {
macAddress = traits.stream().map(t -> t.macAddress).filter(Objects::nonNull).findFirst().orElse(null);
}
List<NhcParameter> parameters = device.parameters;
if (parameters != null) {
mjpegUri = parameters.stream().map(p -> p.mjpegUri).filter(Objects::nonNull).findFirst().orElse(null);
tnUri = parameters.stream().map(p -> p.tnUri).filter(Objects::nonNull).findFirst().orElse(null);
}
List<NhcProperty> properties = device.properties;
if (properties != null) {
ipAddress = properties.stream().map(p -> p.ipAddress).filter(Objects::nonNull).findFirst().orElse(null);
}
logger.debug("adding video device {} model {}, {}", device.uuid, device.model, device.name);
nhcVideo = new NhcVideo2(device.uuid, device.name, device.type, device.technology, device.model, macAddress,
ipAddress, mjpegUri, tnUri, this);
if (macAddress != null) {
NhcVideo2 video = nhcVideo;
String mac = macAddress;
accessDevices.forEach((key, accessDevice) -> {
NhcAccess2 access = (NhcAccess2) accessDevice;
String buttonMac = access.getButtonId();
if (buttonMac != null) {
buttonMac = buttonMac.split("_")[0];
if (mac.equals(buttonMac)) {
int buttonIndex = access.getButtonIndex();
logger.debug("link access device {} to video device {} button {}", accessDevice.getId(),
device.uuid, buttonIndex);
video.setNhcAccess(buttonIndex, access);
access.setNhcVideo(video);
}
}
});
}
}
videoDevices.put(device.uuid, nhcVideo);
}
private void removeDevice(NhcDevice2 device) {
NhcAction action = actions.get(device.uuid);
NhcThermostat thermostat = thermostats.get(device.uuid);
NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
NhcMeter meter = meters.get(device.uuid);
NhcAccess access = accessDevices.get(device.uuid);
NhcVideo video = videoDevices.get(device.uuid);
if (action != null) {
action.actionRemoved();
actions.remove(device.uuid);
} else if (thermostat != null) {
thermostat.thermostatRemoved();
thermostats.remove(device.uuid);
} else if (energyMeter != null) {
energyMeter.energyMeterRemoved();
energyMeters.remove(device.uuid);
} else if (meter != null) {
meter.meterRemoved();
meters.remove(device.uuid);
} else if (access != null) {
access.accessDeviceRemoved();
accessDevices.remove(device.uuid);
} else if (video != null) {
video.videoDeviceRemoved();
videoDevices.remove(device.uuid);
}
}
@ -472,14 +597,22 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
NhcAction action = actions.get(device.uuid);
NhcThermostat thermostat = thermostats.get(device.uuid);
NhcEnergyMeter energyMeter = energyMeters.get(device.uuid);
NhcMeter meter = meters.get(device.uuid);
NhcAccess accessDevice = accessDevices.get(device.uuid);
NhcVideo videoDevice = videoDevices.get(device.uuid);
if (action != null) {
updateActionState((NhcAction2) action, deviceProperties);
} else if (thermostat != null) {
updateThermostatState((NhcThermostat2) thermostat, deviceProperties);
} else if (energyMeter != null) {
updateEnergyMeterState((NhcEnergyMeter2) energyMeter, deviceProperties);
} else if (meter != null) {
updateMeterState((NhcMeter2) meter, deviceProperties);
} else if (accessDevice != null) {
updateAccessState((NhcAccess2) accessDevice, deviceProperties);
} else if (videoDevice != null) {
updateVideoState((NhcVideo2) videoDevice, deviceProperties);
} else {
logger.trace("No known device for {}", device.uuid);
}
}
@ -514,8 +647,8 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
String brightness = dimmerProperty.get().brightness;
if (brightness != null) {
try {
action.setState(Integer.parseInt(brightness));
logger.debug("setting action {} internally to {}", action.getId(), dimmerProperty.get().brightness);
action.setState(Integer.parseInt(brightness));
} catch (NumberFormatException e) {
logger.debug("received invalid brightness value {} for dimmer {}", brightness, action.getId());
}
@ -523,16 +656,16 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
}
if (NHCON.equals(booleanState) || NHCTRUE.equals(booleanState)) {
action.setBooleanState(true);
logger.debug("setting action {} internally to ON", action.getId());
action.setBooleanState(true);
}
}
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("setting action {} internally to {}", action.getId(), position);
action.setState(Integer.parseInt(position));
} catch (NumberFormatException e) {
logger.trace("received empty or invalid rollershutter {} position info {}", action.getId(), position);
}
@ -597,12 +730,12 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
}
logger.debug(
"Niko Home Control: setting thermostat {} with measured {}, setpoint {}, mode {}, overrule {}, overruletime {}, ecosave {}, demand {}",
"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);
thermostat.setState(measured, setpoint, mode, overrule, overruletime, ecosave, demand);
}
private void updateEnergyMeterState(NhcEnergyMeter2 energyMeter, List<NhcProperty> deviceProperties) {
private void updateMeterState(NhcMeter2 meter, List<NhcProperty> deviceProperties) {
try {
Optional<Integer> electricalPower = deviceProperties.stream().map(p -> p.electricalPower)
.map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
@ -614,14 +747,66 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
.map(s -> (!((s == null) || s.isEmpty())) ? Math.round(Float.parseFloat(s)) : null)
.filter(Objects::nonNull).findFirst();
int power = electricalPower.orElse(powerFromGrid.orElse(0) - powerToGrid.orElse(0));
logger.trace("setting energy meter {} power to {}", energyMeter.getId(), electricalPower);
energyMeter.setPower(power);
logger.trace("setting energy meter {} power to {}", meter.getId(), power);
meter.setPower(power);
} catch (NumberFormatException e) {
energyMeter.setPower(null);
logger.trace("wrong format in energy meter {} power reading", energyMeter.getId());
logger.trace("wrong format in energy meter {} power reading", meter.getId());
meter.setPower(null);
}
}
private void updateAccessState(NhcAccess2 accessDevice, List<NhcProperty> deviceProperties) {
Optional<NhcProperty> basicStateProperty = deviceProperties.stream().filter(p -> (p.basicState != null))
.findFirst();
Optional<NhcProperty> doorLockProperty = deviceProperties.stream().filter(p -> (p.doorlock != null))
.findFirst();
if (basicStateProperty.isPresent()) {
String basicState = basicStateProperty.get().basicState;
boolean state = false;
if (NHCON.equals(basicState) || NHCTRUE.equals(basicState)) {
state = true;
}
switch (accessDevice.getType()) {
case RINGANDCOMEIN:
accessDevice.updateRingAndComeInState(state);
logger.debug("setting access device {} ring and come in to {}", accessDevice.getId(), state);
break;
case BELLBUTTON:
accessDevice.updateBellState(state);
logger.debug("setting access device {} bell to {}", accessDevice.getId(), state);
break;
default:
break;
}
}
if (doorLockProperty.isPresent()) {
String doorLockState = doorLockProperty.get().doorlock;
boolean state = false;
if (NHCCLOSED.equals(doorLockState)) {
state = true;
}
logger.debug("setting access device {} doorlock to {}", accessDevice.getId(), state);
accessDevice.updateDoorLockState(state);
}
}
private void updateVideoState(NhcVideo2 videoDevice, List<NhcProperty> deviceProperties) {
String callStatus01 = deviceProperties.stream().map(p -> p.callStatus01).filter(Objects::nonNull).findFirst()
.orElse(null);
String callStatus02 = deviceProperties.stream().map(p -> p.callStatus02).filter(Objects::nonNull).findFirst()
.orElse(null);
String callStatus03 = deviceProperties.stream().map(p -> p.callStatus03).filter(Objects::nonNull).findFirst()
.orElse(null);
String callStatus04 = deviceProperties.stream().map(p -> p.callStatus04).filter(Objects::nonNull).findFirst()
.orElse(null);
logger.debug("setting video device {} call status to {}, {}, {}, {}", videoDevice.getId(), callStatus01,
callStatus02, callStatus03, callStatus04);
videoDevice.updateState(callStatus01, callStatus02, callStatus03, callStatus04);
}
@Override
public void executeAction(String actionId, String value) {
NhcMessage2 message = new NhcMessage2();
@ -770,7 +955,12 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
}
@Override
public void startEnergyMeter(String energyMeterId) {
public void executeMeter(String meterId) {
// Nothing to do, meter readings not supported in NHC II at this point in time
}
@Override
public void retriggerMeterLive(String meterId) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
@ -782,7 +972,7 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = energyMeterId;
device.uuid = meterId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty reportInstantUsageProp = new NhcProperty();
@ -793,27 +983,133 @@ public class NikoHomeControlCommunication2 extends NikoHomeControlCommunication
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
if (energyMeter != null) {
energyMeter.startEnergyMeter(topic, gsonMessage);
}
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void stopEnergyMeter(String energyMeterId) {
NhcEnergyMeter2 energyMeter = (NhcEnergyMeter2) energyMeters.get(energyMeterId);
if (energyMeter != null) {
energyMeter.stopEnergyMeter();
public void executeAccessBell(String accessId) {
executeAccess(accessId);
}
@Override
public void executeAccessRingAndComeIn(String accessId, boolean ringAndComeIn) {
NhcAccess2 accessDevice = (NhcAccess2) accessDevices.get(accessId);
if (accessDevice == null) {
return;
}
boolean current = accessDevice.getRingAndComeInState();
if ((ringAndComeIn && !current) || (!ringAndComeIn && current)) {
executeAccess(accessId);
} else {
logger.trace("Not updating ring and come in as state did not change");
}
}
/**
* 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) {
private void executeAccess(String accessId) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
List<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
List<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = accessId;
List<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty property = new NhcProperty();
deviceProperties.add(property);
device.properties = deviceProperties;
NhcAccess2 accessDevice = (NhcAccess2) accessDevices.get(accessId);
if (accessDevice == null) {
return;
}
property.basicState = NHCTRIGGERED;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeVideoBell(String accessId, int buttonIndex) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
List<NhcMessageParam> params = new ArrayList<>();
NhcMessageParam param = new NhcMessageParam();
params.add(param);
message.params = params;
List<NhcDevice2> devices = new ArrayList<>();
NhcDevice2 device = new NhcDevice2();
devices.add(device);
param.devices = devices;
device.uuid = accessId;
List<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty property = new NhcProperty();
deviceProperties.add(property);
device.properties = deviceProperties;
NhcVideo videoDevice = videoDevices.get(accessId);
if (videoDevice == null) {
return;
}
switch (buttonIndex) {
case 1:
property.callStatus01 = NHCRINGING;
break;
case 2:
property.callStatus02 = NHCRINGING;
break;
case 3:
property.callStatus03 = NHCRINGING;
break;
case 4:
property.callStatus04 = NHCRINGING;
break;
default:
break;
}
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}
@Override
public void executeAccessUnlock(String accessId) {
NhcMessage2 message = new NhcMessage2();
message.method = "devices.control";
List<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 = accessId;
ArrayList<NhcProperty> deviceProperties = new ArrayList<>();
NhcProperty property = new NhcProperty();
deviceProperties.add(property);
device.properties = deviceProperties;
NhcAccess2 accessDevice = (NhcAccess2) accessDevices.get(accessId);
if (accessDevice == null) {
return;
}
property.doorlock = NHCOPEN;
String topic = profile + "/control/devices/cmd";
String gsonMessage = gson.toJson(message);
sendDeviceMessage(topic, gsonMessage);
}

View File

@ -43,11 +43,26 @@ blindDescription = Rollershutter type action in Niko Home Control
thermostatLabel = Thermostat
thermostatDescription = Thermostat in the Niko Home Control system
energyMeterLiveLabel = Energy Meter Live
energyMeterLiveDescription = Energy meter with live power consumption in the Niko Home Control system
energyMeterLabel = Energy Meter
energyMeterDescription = Energy meter in the Niko Home Control system
actionConfigActionIdLabel = Action ID
actionConfigActionIdDescription = Niko Home Control action ID
gasMeterLabel = Gas Meter
gasMeterDescription = Gas meter in the Niko Home Control system
waterMeterLabel = Water Meter
waterMeterDescription = Water meter in the Niko Home Control system
accessLabel = Access Control
accessDescription = Basic Access Control in the Niko Home Control system
accessRingAndComeInLabel = Ring And Come In Access Control
accessRingAndComeInDescription = Access Control with ring and come in function in the Niko Home Control system
deviceConfigDeviceIdLabel = Device ID
deviceConfigDeviceIdDescription = Niko Home Control device ID
dimmerConfigStepLabel = Step Value
dimmerConfigStepDescription = Step value used for increase/decrease of dimmer brightness, default 10%
@ -55,15 +70,16 @@ dimmerConfigStepDescription = Step value used for increase/decrease of dimmer br
blindConfigInvertLabel = Invert Direction
blindConfigInvertDescription = Invert rollershutter direction
thermostatConfigThermostatIdLabel = Thermostat ID
thermostatConfigThermostatIdDescription = Niko Home Control Thermostat ID
thermostatConfigOverruleTimeLabel = Overrule Time
thermostatConfigOverruleTimeDescription = Default overrule duration in minutes when an overrule temperature is set without providing overrule \
time, 60 minutes by default
energyMeterConfigEnergyMeterIdLabel = Energy Meter ID
energyMeterConfigEnergyMeterIdDescription = Niko Home Control Energy Meter ID
meterConfigInvertLabel = Invert Reading
meterConfigInvertDescription = Invert the reading sign, this allows production meters to be represented with positive values for production
meterConfigRefreshLabel = Refresh Interval
meterConfigRefreshDescription = Refresh interval for meter reading in minutes, default 10 minutes. The value should not be lower than 5 minutes to \
avoid too many meter data retrieval calls
#channel types
channelButtonLabel = Button
@ -101,6 +117,36 @@ channelDemand1 = Heating
channelPowerLabel = Power
channelPowerDescription = Momentary power consumption/production (positive is consumption)
channelEnergyLabel = Energy
channelEnergyDescription = Energy consumption/production (positive is consumption)
channelEnergyDayLabel = Energy Today
channelEnergyDayDescription = Today's energy consumption/production (positive is consumption)
channelGasLabel = Gas
channelGasDescription = Gas consumption
channelGasDayLabel = Gas Today
channelGasDayDescription = Today's gas consumption
channelWaterLabel = Water
channelWaterDescription = Water consumption
channelWaterDayLabel = Water Today
channelWaterDayDescription = Today's water consumption
channelMeasurementTimeLabel = Measurement Time
channelMeasurementTimeDescription = Date and time of last meter reading
channelBellButtonLabel = Bell Button
channelBellButtonDescription = Bell status and control for door
channelRingAndComeInLabel = Ring and Come In
channelRingAndComeInDescription = Ring and come in status and turn on/off
channelLockLabel = Lock
channelLockDescription = Doorlock status and unlock door
channelAlarmLabel = Alarm
channelAlarmDescription = Alarm from Niko Home Control
@ -112,16 +158,13 @@ offline.configuration-error.ip = Cannot resolve bridge IP with given host name
offline.configuration-error.tokenEmpty = Hobby API token is empty
offline.configuration-error.tokenExpired = Hobby API token has expired
offline.configuration-error.actionId = Configured action ID does not match an action in controller
offline.configuration-error.deviceId = Configured device ID does not match a device in controller
offline.configuration-error.deviceRemoved = Device has been removed from controller
offline.configuration-error.actionType = Unsupported action type
offline.configuration-error.actionRemoved = Action has been removed from controller
offline.configuration-error.energyMeterId = Configured energy meter ID does not match an energy meter in controller
offline.configuration-error.energyMeterRemoved = Energy meter has been removed from controller
offline.configuration-error.thermostatId = Configured thermostat ID does not match a thermostat in controller
offline.configuration-error.thermostatRemoved = Thermostat has been removed from controller
offline.configuration-error.meterType = Unsupported meter type
offline.configuration-error.invalid-bridge-handler = Invalid bridge handler
offline.bridge-unitialized = Bridge unitialized
offline.communication-error = Error communicating with controller

View File

@ -15,7 +15,6 @@
<parameter name="addr" type="text" required="true">
<label>@text/bridgeConfigAddressLabel</label>
<description>@text/bridgeConfigAddressDescription</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
@ -43,7 +42,6 @@
<parameter name="addr" type="text" required="true">
<label>@text/bridgeConfigAddressLabel</label>
<description>@text/bridge2ConfigAddressDescription</description>
<advanced>false</advanced>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer">
@ -84,9 +82,8 @@
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>@text/actionConfigActionIdLabel</label>
<description>@text/actionConfigActionIdDescription</description>
<advanced>false</advanced>
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
</config-description>
</thing-type>
@ -102,9 +99,8 @@
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>@text/actionConfigActionIdLabel</label>
<description>@text/actionConfigActionIdDescription</description>
<advanced>false</advanced>
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
</config-description>
</thing-type>
@ -120,9 +116,8 @@
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>@text/actionConfigActionIdLabel</label>
<description>@text/actionConfigActionIdDescription</description>
<advanced>false</advanced>
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="step" type="integer">
<label>@text/dimmerConfigStepLabel</label>
@ -144,9 +139,8 @@
</channels>
<config-description>
<parameter name="actionId" type="text" required="true">
<label>@text/actionConfigActionIdLabel</label>
<description>@text/actionConfigActionIdDescription</description>
<advanced>false</advanced>
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="invert" type="boolean">
<label>@text/blindConfigInvertLabel</label>
@ -174,9 +168,8 @@
</channels>
<config-description>
<parameter name="thermostatId" type="text" required="true">
<label>@text/thermostatConfigThermostatIdLabel</label>
<description>@text/thermostatConfigThermostatIdDescription</description>
<advanced>false</advanced>
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="overruleTime" type="integer">
<label>@text/thermostatConfigOverruleTimeLabel</label>
@ -186,20 +179,160 @@
</parameter>
</config-description>
</thing-type>
<thing-type id="energyMeterLive">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>@text/energyMeterLiveLabel</label>
<description>@text/energyMeterLiveDescription</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="energy" typeId="energy"/>
<channel id="energyday" typeId="energyday"/>
<channel id="energylast" typeId="measurementtime"/>
</channels>
<config-description>
<parameter name="meterId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="refresh" type="integer" min="5">
<label>@text/meterConfigRefreshLabel</label>
<description>@text/meterConfigRefreshDescription</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="invert" type="boolean">
<label>@text/meterConfigInvertLabel</label>
<description>@text/meterConfigInvertDescription</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="energyMeter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge2"/>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>@text/energyMeterLabel</label>
<description>@text/energyMeterDescription</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="energy" typeId="energy"/>
<channel id="energyday" typeId="energyday"/>
<channel id="energylast" typeId="measurementtime"/>
</channels>
<config-description>
<parameter name="energyMeterId" type="text" required="true">
<label>@text/energyMeterConfigEnergyMeterIdLabel</label>
<description>@text/energyMeterConfigEnergyMeterIdDescription</description>
<advanced>false</advanced>
<parameter name="meterId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="refresh" type="integer" min="5">
<label>@text/meterConfigRefreshLabel</label>
<description>@text/meterConfigRefreshDescription</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="invert" type="boolean">
<label>@text/meterConfigInvertLabel</label>
<description>@text/meterConfigInvertDescription</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="gasMeter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>@text/gasMeterLabel</label>
<description>@text/gasMeterDescription</description>
<channels>
<channel id="gas" typeId="gas"/>
<channel id="gasday" typeId="gasday"/>
<channel id="gaslast" typeId="measurementtime"/>
</channels>
<config-description>
<parameter name="meterId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="refresh" type="integer" min="5">
<label>@text/meterConfigRefreshLabel</label>
<description>@text/meterConfigRefreshDescription</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="invert" type="boolean">
<label>@text/meterConfigInvertLabel</label>
<description>@text/meterConfigInvertDescription</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="waterMeter">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>@text/waterMeterLabel</label>
<description>@text/waterMeterDescription</description>
<channels>
<channel id="water" typeId="water"/>
<channel id="waterday" typeId="waterday"/>
<channel id="waterlast" typeId="measurementtime"/>
</channels>
<config-description>
<parameter name="meterId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
<parameter name="refresh" type="integer" min="5">
<label>@text/meterConfigRefreshLabel</label>
<description>@text/meterConfigRefreshDescription</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="invert" type="boolean">
<label>@text/meterConfigInvertLabel</label>
<description>@text/meterConfigInvertDescription</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<thing-type id="access">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>@text/accessLabel</label>
<description>@text/accessDescription</description>
<channels>
<channel id="bellbutton" typeId="bellbutton"/>
<channel id="lock" typeId="lock"/>
</channels>
<config-description>
<parameter name="accessId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
</config-description>
</thing-type>
<thing-type id="accessRingAndComeIn">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge2"/>
</supported-bridge-type-refs>
<label>@text/accessRingAndComeInLabel</label>
<description>@text/accessRingAndComeInDescription</description>
<channels>
<channel id="bellbutton" typeId="bellbutton"/>
<channel id="ringandcomein" typeId="ringandcomein"/>
<channel id="lock" typeId="lock"/>
</channels>
<config-description>
<parameter name="accessId" type="text" required="true">
<label>@text/deviceConfigDeviceIdLabel</label>
<description>@text/deviceConfigDeviceIdDescription</description>
</parameter>
</config-description>
</thing-type>
@ -244,7 +377,6 @@
<item-type>Number</item-type>
<label>@text/channelOverruletimeLabel</label>
<description>@text/channelOverruletimeDescription</description>
<category>Number</category>
<state min="0" max="1440" step="5"/>
</channel-type>
<channel-type id="heatingmode">
@ -310,10 +442,71 @@
<item-type>Number:Power</item-type>
<label>@text/channelPowerLabel</label>
<description>@text/channelPowerDescription</description>
<category>Number</category>
<state readOnly="true" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="energy">
<item-type>Number:Energy</item-type>
<label>@text/channelEnergyLabel</label>
<description>@text/channelEnergyDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="energyday">
<item-type>Number:Energy</item-type>
<label>@text/channelEnergyDayLabel</label>
<description>@text/channelEnergyDayDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="gas">
<item-type>Number:Volume</item-type>
<label>@text/channelGasLabel</label>
<description>@text/channelGasDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="gasday">
<item-type>Number:Volume</item-type>
<label>@text/channelGasDayLabel</label>
<description>@text/channelGasDayDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="water">
<item-type>Number:Volume</item-type>
<label>@text/channelWaterLabel</label>
<description>@text/channelWaterDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="waterday">
<item-type>Number:Volume</item-type>
<label>@text/channelWaterDayLabel</label>
<description>@text/channelWaterDayDescription</description>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="measurementtime">
<item-type>DateTime</item-type>
<label>@text/channelMeasurementTimeLabel</label>
<description>@text/channelMeasurementTimeDescription</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="bellbutton">
<item-type>Switch</item-type>
<label>@text/channelBellButtonLabel</label>
<description>@text/channelBellButtonDescription</description>
<category>Switch</category>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="ringandcomein">
<item-type>Switch</item-type>
<label>@text/channelRingAndComeInLabel</label>
<description>@text/channelRingAndComeInDescription</description>
<category>Switch</category>
</channel-type>
<channel-type id="lock">
<item-type>Switch</item-type>
<label>@text/channelLockLabel</label>
<description>@text/channelLockDescription</description>
<category>Switch</category>
<autoUpdatePolicy>veto</autoUpdatePolicy>
</channel-type>
<channel-type id="alarm">
<kind>trigger</kind>
@ -325,5 +518,4 @@
<label>@text/channelNoticeLabel</label>
<description>@text/channelNoticeDescription</description>
</channel-type>
</thing:thing-descriptions>