mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[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:
parent
b34f66c148
commit
6c858df407
@ -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:
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 = "";
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -176,6 +176,7 @@ public abstract class NikoHomeControlBridgeHandler extends BaseBridgeHandler imp
|
||||
|
||||
NikoHomeControlCommunication comm = nhcComm;
|
||||
if (comm != null) {
|
||||
comm.stopAllMeters();
|
||||
comm.stopCommunication();
|
||||
}
|
||||
nhcComm = null;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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, < 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
|
||||
|
@ -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, < 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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<String, {@link NhcEnergyMeter}></code>
|
||||
* @return <code>Map<String, {@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<String, {@link NhcAccess}></code>
|
||||
*/
|
||||
public Map<String, NhcAccess> getAccessDevices() {
|
||||
return accessDevices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all video devices in the Niko Home Control Controller.
|
||||
*
|
||||
* @return <code>Map<String, {@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) {
|
||||
}
|
||||
}
|
||||
|
@ -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" };
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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 = "";
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user