mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[nobohub] Initial contribution (#12937)
* Added NoboHub binding. Signed-off-by: Espen Fossen <espenaf@junta.no>
This commit is contained in:
parent
9c2070f748
commit
bc9cf8e07a
@ -223,6 +223,7 @@
|
||||
/bundles/org.openhab.binding.nibeuplink/ @alexf2015
|
||||
/bundles/org.openhab.binding.nikobus/ @crnjan
|
||||
/bundles/org.openhab.binding.nikohomecontrol/ @mherwege
|
||||
/bundles/org.openhab.binding.nobohub/ @espenaf
|
||||
/bundles/org.openhab.binding.novafinedust/ @t2000
|
||||
/bundles/org.openhab.binding.ntp/ @marcelrv
|
||||
/bundles/org.openhab.binding.nuki/ @janvyb
|
||||
|
@ -1111,6 +1111,11 @@
|
||||
<artifactId>org.openhab.binding.nikohomecontrol</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.nobohub</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.novafinedust</artifactId>
|
||||
|
13
bundles/org.openhab.binding.nobohub/NOTICE
Normal file
13
bundles/org.openhab.binding.nobohub/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
167
bundles/org.openhab.binding.nobohub/README.md
Normal file
167
bundles/org.openhab.binding.nobohub/README.md
Normal file
@ -0,0 +1,167 @@
|
||||
# NoboHub Binding
|
||||
|
||||
This binding controls the Glen Dimplex Nobø Hub using the <a href="https://www.glendimplex.se/media/15650/nobo-hub-api-v-1-1-integration-for-advanced-users.pdf">Nobø Hub API v1.1</a>.
|
||||
|
||||
![Nobo Hub](doc/nobohub.jpg)
|
||||
|
||||
It lets you read and change temperature and profile settings for zones, and read and set active overrides to change the global mode of the hub.
|
||||
|
||||
This binding is tested with the following devices:
|
||||
|
||||
* Thermostats for different electrical panel heaters
|
||||
* Thermostats for heating in floors
|
||||
* Nobø Switch SW 4
|
||||
|
||||
## Thermostats
|
||||
|
||||
Not all thermostats are made equal.
|
||||
|
||||
* NCU-1R: Comfort temperature setting on the device overrides values from the Hub, making the setting in the Hub useless.
|
||||
* NCU-2R: Synchronizes temperature settings to and from the Hub.
|
||||
|
||||
## Supported Things
|
||||
|
||||
| Thing | Thing Type | Description |
|
||||
|-----------|------------|-------------------------------------------------------------------------------------------------|
|
||||
| hub | Bridge | The Nobø Hub provides a gateway between your components, with the ability to organise in zones. |
|
||||
| component | Thing | A component is a device, i.e. panel heater or switch. |
|
||||
| zone | Thing | A zone can hold one or more components. |
|
||||
|
||||
|
||||
## Discovery
|
||||
|
||||
The hub will be automatically discovered.
|
||||
Before it can be used, you will have to update the configuration with the last three digits of its serial number.
|
||||
|
||||
When the hub is configured with the correct serial number, it will autodetect zones and components (thermostats and switches).
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
```
|
||||
# Configuration for Nobø Hub
|
||||
#
|
||||
# Serial number of the Nobø hub to communicate with, 12 digits.
|
||||
serialNumber=103000xxxxxx
|
||||
|
||||
# Host name or IP address of the Nobø hub
|
||||
hostName=10.0.0.10
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
### Hub
|
||||
|
||||
| channel | type | description |
|
||||
|---------------------|--------|-----------------------------------------------------|
|
||||
| activeOverrideName | String | The name of the active override |
|
||||
|
||||
### Zone
|
||||
|
||||
| channel | type | description |
|
||||
|------------------------------|--------------------|--------------------------------------------|
|
||||
| activeWeekProfileName | String | The name of the active week profile |
|
||||
| activeWeekProfile | Number | The active week profile id |
|
||||
| comfortTemperature | Number:Temperature | The configured comfort temperature |
|
||||
| ecoTemperature | Number:Temperature | The configured eco temparature |
|
||||
| currentTemperature | Number:Temperature | The current temperature in the zone |
|
||||
| calculatedWeekProfileStatus | String | The current override based on week profile |
|
||||
|
||||
CurrentTemperature only works if the zone has a device that reports it (e.g. a switch).
|
||||
|
||||
### Component
|
||||
|
||||
| channel | type | description |
|
||||
|---------------------|--------------------|------------------------------------------|
|
||||
| currentTemperature | Number:Temperature | The current temperature of the component |
|
||||
|
||||
Not all devices report this.
|
||||
|
||||
## Full Example
|
||||
|
||||
### nobo.things
|
||||
|
||||
```
|
||||
Bridge nobohub:nobohub:controller "Nobø Hub" [ hostName="192.168.1.10", serialNumber="103000000000" ] {
|
||||
Thing zone 1 "Zone - Kitchen" [ id=1 ]
|
||||
Thing component 184000000000 "Heater - Kitchen" [ serialNumber="184000000000" ]
|
||||
}
|
||||
```
|
||||
|
||||
### nobo.items
|
||||
|
||||
```
|
||||
// Hub
|
||||
String Nobo_Hub_GlobalOverride "Global Override %s" <heating> {channel="nobohub:nobohub:controller:activeOverrideName"}
|
||||
|
||||
// Panel Heater
|
||||
Number:Temperature PanelHeater_CurrentTemperature "Setpoint [%.1f °C]" <temperature> {channel="nobohub:component:controller:184000000000:currentTemperature"}
|
||||
|
||||
// Zone
|
||||
String Zone_ActiveWeekProfileName "Active week profile name [%s]" <calendar> {channel="nobohub:zone:controller:1:activeWeekProfileName"}
|
||||
Number Zone_ActiveWeekProfile "Active week profile [%d]" <calendar> {channel="nobohub:zone:controller:1:activeWeekProfile"}
|
||||
String Zone_ActiveStatus "Active status %s]" <heating> {channel="nobohub:zone:controller:1:calculatedWeekProfileStatus"}
|
||||
Number:Temperature Zone_ComfortTemperature "Comfort temperature [%.1f °C]" <temperature> {channel="nobohub:zone:controller:1:comfortTemperature"}
|
||||
Number:Temperature Zone_EcoTemperatur "Eco temperature [%.1f °C]" <temperature> {channel="nobohub:zone:controller:1:ecoTemperature"}
|
||||
Number:Temperature Zone_CurrentTemperature "Current temperature [%.1f °C]" <temperature> {channel="nobohub:zone:controller:1:currentTemperature"}
|
||||
```
|
||||
|
||||
### nobo.sitemap
|
||||
|
||||
```
|
||||
sitemap nobo label="Nobø " {
|
||||
|
||||
Frame label="Hub"{
|
||||
Switch item=Nobo_Hub_GlobalOverride
|
||||
}
|
||||
|
||||
Frame label="Main Bedroom"{
|
||||
Switch item=Zone_ActiveStatus
|
||||
Text item=Zone_ActiveWeekProfileName
|
||||
Text item=Zone_ActiveWeekProfile
|
||||
Selection item=Zone_ActiveWeekProfile
|
||||
Setpoint item=Zone_ComfortTemperatur minValue=7 maxValue=30 step=1 icon="temperature"
|
||||
Setpoint item=Zone_EcoTemperatur minValue=7 maxValue=30 step=1 icon="temperature"
|
||||
Text item=Zone_CurrentTemperatur
|
||||
Text item=PanelHeater_CurrentTemperatur
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Organize your setup
|
||||
|
||||
Nobø Hub uses a combination of status types (Normal, Comfort, Eco, Away), profiles types (Comfort, Eco, Away, Off), predefined temperature types (Comfort, Eco, Away), zones and override settings to organize and enable different features.
|
||||
This makes it possible to control the heaters in many different scenarios and combinations.
|
||||
The following is a suggested way of organizing the binding with the Hub for a good level of control and flexibility.
|
||||
|
||||
If you own panels with a physical Comfort temperature override, you need to use the Eco temperature type for setting level used by the day based profiles.
|
||||
If not, you can use either Comfort or Eco to set wanted level.
|
||||
|
||||
Start by creating the following profiles in the Nobø Hub App:
|
||||
|
||||
OFF Set to status off all day, every day.
|
||||
ON Set to status [Comfort|Eco] all day, every day
|
||||
Eco Set to status Eco all day, every day
|
||||
Away Set to status Away all way, every day
|
||||
Weekday 06->16 Set to status [Comfort|Eco] between 06->16 every weekday, otherwise set to [Away|Off]
|
||||
Weekday 06->23 Set to status [Comfort|Eco] between 06->23 every weekday, otherwise set to [Away|Off]
|
||||
Weekend 06->16 Set to status [Comfort|Eco] between 06->16 in the weekend, otherwise set to [Away|Off]
|
||||
Weekend 06->23 Set to status [Comfort|Eco] between 06->23 in the weekend, otherwise set to [Away|Off]
|
||||
Every day 06->16 Set to status [Comfort|Eco] between 06->16 every day, otherwise set to [Away|Off]
|
||||
Every day 06->23 Set to status [Comfort|Eco] between 06->23 every day, otherwise set to [Away|Off]
|
||||
|
||||
Next set [Comfort|Eco] level for each zone to your requirements.
|
||||
For a more advanced setup, you can create a rule which both sets temperature level and profile.
|
||||
|
||||
Then create a sitemap with a Selection pointing to the Week Profile item.
|
||||
The binding will now automatically update all available week profile options in the selection button:
|
||||
|
||||
### nobo.sitemap
|
||||
|
||||
```
|
||||
sitemap nobo label="Nobø " {
|
||||
|
||||
Frame label="Main Bedroom"{
|
||||
Selection item=MainBedroom_Zone_WeekProfile
|
||||
}
|
||||
}
|
||||
```
|
BIN
bundles/org.openhab.binding.nobohub/doc/nobohub.jpg
Normal file
BIN
bundles/org.openhab.binding.nobohub/doc/nobohub.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
17
bundles/org.openhab.binding.nobohub/pom.xml
Normal file
17
bundles/org.openhab.binding.nobohub/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.4.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.nobohub</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: NoboHub Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.nobohub-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-nobohub" description="NoboHub Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.nobohub/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ComponentConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ComponentConfiguration {
|
||||
|
||||
/**
|
||||
* Serial number of the component.
|
||||
*/
|
||||
@Nullable
|
||||
public String serialNumber;
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_COMPONENT_CURRENT_TEMPERATURE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_MODEL;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_TEMPERATURE_SENSOR_FOR_ZONE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_ZONE;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.model.Component;
|
||||
import org.openhab.binding.nobohub.internal.model.SerialNumber;
|
||||
import org.openhab.binding.nobohub.internal.model.Zone;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Shows information about a Component in the Nobø Hub.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ComponentHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ComponentHandler.class);
|
||||
|
||||
private final NoboHubTranslationProvider messages;
|
||||
|
||||
protected @Nullable SerialNumber serialNumber;
|
||||
|
||||
public ComponentHandler(Thing thing, NoboHubTranslationProvider messages) {
|
||||
super(thing);
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
public void onUpdate(Component component) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
double temp = component.getTemperature();
|
||||
if (!Double.isNaN(temp)) {
|
||||
QuantityType<Temperature> currentTemperature = new QuantityType<>(temp, SIUnits.CELSIUS);
|
||||
updateState(CHANNEL_COMPONENT_CURRENT_TEMPERATURE, currentTemperature);
|
||||
}
|
||||
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, component.getSerialNumber().toString());
|
||||
properties.put(PROPERTY_NAME, component.getName());
|
||||
properties.put(PROPERTY_MODEL, component.getSerialNumber().getComponentType());
|
||||
|
||||
String zoneName = getZoneName(component.getZoneId());
|
||||
if (zoneName != null) {
|
||||
properties.put(PROPERTY_ZONE, zoneName);
|
||||
}
|
||||
|
||||
String tempForZoneName = getZoneName(component.getTemperatureSensorForZoneId());
|
||||
if (tempForZoneName != null) {
|
||||
properties.put(PROPERTY_TEMPERATURE_SENSOR_FOR_ZONE, tempForZoneName);
|
||||
}
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
private @Nullable String getZoneName(int zoneId) {
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
if (hubHandler != null) {
|
||||
Zone zone = hubHandler.getZone(zoneId);
|
||||
if (null != zone) {
|
||||
return zone.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
String serialNumberString = getConfigAs(ComponentConfiguration.class).serialNumber;
|
||||
if (serialNumberString != null && !serialNumberString.isEmpty()) {
|
||||
SerialNumber sn = new SerialNumber(serialNumberString);
|
||||
if (!sn.isWellFormed()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/message.component.illegal.serial [\"" + serialNumberString + "\"]");
|
||||
} else {
|
||||
this.serialNumber = sn;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/message.missing.serial");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refreshing channel {}", channelUID);
|
||||
if (null != serialNumber) {
|
||||
Component component = getComponent();
|
||||
if (null == component) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
|
||||
messages.getText("message.component.notfound", serialNumber, channelUID));
|
||||
} else {
|
||||
onUpdate(component);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
|
||||
"@text/message.component.missing.id [\"" + channelUID + "\"]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("This component is a read-only device and cannot handle commands.");
|
||||
}
|
||||
|
||||
public @Nullable SerialNumber getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
private @Nullable Component getComponent() {
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
SerialNumber serialNumber = this.serialNumber;
|
||||
if (null != serialNumber && null != hubHandler) {
|
||||
return hubHandler.getComponent(serialNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Shows information about a Component in the Nobø Hub.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Helpers {
|
||||
|
||||
public static String formatDuration(Duration duration) {
|
||||
long seconds = duration.getSeconds();
|
||||
long absSeconds = Math.abs(seconds);
|
||||
String positive = String.format("%d:%02d:%02d", absSeconds / 3600, (absSeconds % 3600) / 60, absSeconds % 60);
|
||||
return seconds < 0 ? "-" + positive : positive;
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link NoboHubBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboHubBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "nobohub";
|
||||
|
||||
public static final String API_VERSION = "1.1";
|
||||
|
||||
public static final String PROPERTY_NAME = "name";
|
||||
public static final String PROPERTY_MODEL = "model";
|
||||
public static final String PROPERTY_HOSTNAME = "hostName";
|
||||
|
||||
public static final String PROPERTY_VENDOR_NAME = "Glen Dimplex Nobø";
|
||||
public static final String PROPERTY_PRODUCTION_DATE = "productionDate";
|
||||
|
||||
public static final String PROPERTY_SOFTWARE_VERSION = "softwareVersion";
|
||||
|
||||
public static final String PROPERTY_ZONE = "zone";
|
||||
public static final String PROPERTY_ZONE_ID = "id";
|
||||
public static final String PROPERTY_TEMPERATURE_SENSOR_FOR_ZONE = "temperatureSensorForZone";
|
||||
|
||||
public static final int NOBO_HUB_TCP_PORT = 27779;
|
||||
|
||||
public static final Duration TIME_BETWEEN_FULL_SCANS = Duration.ofMinutes(10);
|
||||
public static final Duration TIME_BETWEEN_RETRIES_ON_ERROR = Duration.ofSeconds(10);
|
||||
|
||||
public static final Duration RECOMMENDED_KEEPALIVE_INTERVAL = Duration.ofSeconds(14);
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_HUB = new ThingTypeUID(BINDING_ID, "nobohub");
|
||||
public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
|
||||
public static final ThingTypeUID THING_TYPE_COMPONENT = new ThingTypeUID(BINDING_ID, "component");
|
||||
|
||||
public static final Set<ThingTypeUID> AUTODISCOVERED_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(THING_TYPE_ZONE, THING_TYPE_COMPONENT));
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(
|
||||
Arrays.asList(THING_TYPE_HUB, THING_TYPE_ZONE, THING_TYPE_COMPONENT));
|
||||
|
||||
// List of all Channel ids
|
||||
|
||||
// Hub
|
||||
public static final String CHANNEL_HUB_ACTIVE_OVERRIDE_NAME = "activeOverrideName";
|
||||
|
||||
// Zone
|
||||
public static final String CHANNEL_ZONE_ACTIVE_WEEK_PROFILE_NAME = "activeWeekProfileName";
|
||||
public static final String CHANNEL_ZONE_ACTIVE_WEEK_PROFILE = "activeWeekProfile";
|
||||
public static final String CHANNEL_ZONE_CALCULATED_WEEK_PROFILE_STATUS = "calculatedWeekProfileStatus";
|
||||
public static final String CHANNEL_ZONE_COMFORT_TEMPERATURE = "comfortTemperature";
|
||||
public static final String CHANNEL_ZONE_ECO_TEMPERATURE = "ecoTemperature";
|
||||
public static final String CHANNEL_ZONE_CURRENT_TEMPERATURE = "currentTemperature";
|
||||
|
||||
// Component
|
||||
public static final String CHANNEL_COMPONENT_CURRENT_TEMPERATURE = "currentTemperature";
|
||||
|
||||
// Date/time
|
||||
public static final DateTimeFormatter DATE_FORMAT_SECONDS = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
|
||||
public static final DateTimeFormatter DATE_FORMAT_MINUTES = DateTimeFormatter.ofPattern("yyyyMMddHHmm");
|
||||
public static final DateTimeFormatter TIME_FORMAT_MINUTES = DateTimeFormatter.ofPattern("HHmm");
|
||||
|
||||
// Discovery
|
||||
public static final int NOBO_HUB_BROADCAST_PORT = 10000;
|
||||
public static final String NOBO_HUB_BROADCAST_ADDRESS = "0.0.0.0";
|
||||
public static final int NOBO_HUB_MULTICAST_PORT = 10001;
|
||||
public static final String NOBO_HUB_MULTICAST_ADDRESS = "239.0.1.187";
|
||||
|
||||
// Mappings
|
||||
|
||||
public static final Map<String, String> REJECT_REASONS = Stream.of(new String[][] {
|
||||
{ "0", "Client command set too old. Please run with debug logs." },
|
||||
{ "1", "Hub serial number mismatch. Should be 12 digits, if hub was autodetected, please add the last three." },
|
||||
{ "2", "Wrong number of arguments. Please run with debug logs." },
|
||||
{ "3", "Timestamp incorrectly formatted. Please run with debug logs." }, })
|
||||
.collect(Collectors.collectingAndThen(Collectors.toMap(data -> data[0], data -> data[1]),
|
||||
Collections::<String, String> unmodifiableMap));
|
||||
|
||||
// Full list of units: https://help.nobo.no/skriver/?chapterid=344&chapterlanguageid=2
|
||||
public static final Map<String, String> SERIALNUMBERS_FOR_TYPES = Stream
|
||||
.of(new String[][] { { "120", "RS-700" }, { "168", "NCU-2R" }, { "184", "NCU-1R" }, { "186", "NTD-4R" },
|
||||
{ "192", "TXF" }, { "198", "NCU-ER" }, { "210", "NTB-2R" }, { "234", "Nobø Switch" }, })
|
||||
.collect(Collectors.collectingAndThen(Collectors.toMap(data -> data[0], data -> data[1]),
|
||||
Collections::<String, String> unmodifiableMap));
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NoboHubBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboHubBridgeConfiguration {
|
||||
|
||||
/**
|
||||
* Serial number of Nobø Hub.
|
||||
*/
|
||||
@Nullable
|
||||
public String serialNumber;
|
||||
|
||||
/**
|
||||
* Host address of Nobø Hub.
|
||||
*/
|
||||
@Nullable
|
||||
public String hostName;
|
||||
|
||||
/**
|
||||
* Polling interval (seconds)
|
||||
*/
|
||||
public int pollingInterval;
|
||||
}
|
@ -0,0 +1,418 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_HUB_ACTIVE_OVERRIDE_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_HOSTNAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_PRODUCTION_DATE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_SOFTWARE_VERSION;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.RECOMMENDED_KEEPALIVE_INTERVAL;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.connection.HubCommunicationThread;
|
||||
import org.openhab.binding.nobohub.internal.connection.HubConnection;
|
||||
import org.openhab.binding.nobohub.internal.discovery.NoboThingDiscoveryService;
|
||||
import org.openhab.binding.nobohub.internal.model.Component;
|
||||
import org.openhab.binding.nobohub.internal.model.ComponentRegister;
|
||||
import org.openhab.binding.nobohub.internal.model.Hub;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboCommunicationException;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboDataException;
|
||||
import org.openhab.binding.nobohub.internal.model.OverrideMode;
|
||||
import org.openhab.binding.nobohub.internal.model.OverridePlan;
|
||||
import org.openhab.binding.nobohub.internal.model.OverrideRegister;
|
||||
import org.openhab.binding.nobohub.internal.model.SerialNumber;
|
||||
import org.openhab.binding.nobohub.internal.model.Temperature;
|
||||
import org.openhab.binding.nobohub.internal.model.WeekProfile;
|
||||
import org.openhab.binding.nobohub.internal.model.WeekProfileRegister;
|
||||
import org.openhab.binding.nobohub.internal.model.Zone;
|
||||
import org.openhab.binding.nobohub.internal.model.ZoneRegister;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NoboHubBridgeHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboHubBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NoboHubBridgeHandler.class);
|
||||
private @Nullable HubCommunicationThread hubThread;
|
||||
private @Nullable NoboThingDiscoveryService discoveryService;
|
||||
private @Nullable Hub hub;
|
||||
|
||||
private final OverrideRegister overrideRegister = new OverrideRegister();
|
||||
private final WeekProfileRegister weekProfileRegister = new WeekProfileRegister();
|
||||
private final ZoneRegister zoneRegister = new ZoneRegister();
|
||||
private final ComponentRegister componentRegister = new ComponentRegister();
|
||||
|
||||
public NoboHubBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.info("Handle command {} for channel {}!", command.toFullString(), channelUID);
|
||||
|
||||
HubCommunicationThread ht = this.hubThread;
|
||||
Hub h = this.hub;
|
||||
if (command instanceof RefreshType) {
|
||||
try {
|
||||
if (ht != null) {
|
||||
ht.getConnection().refreshAll();
|
||||
}
|
||||
} catch (NoboCommunicationException noboEx) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/message.bridge.status.failed [\"" + noboEx.getMessage() + "\"]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHANNEL_HUB_ACTIVE_OVERRIDE_NAME.equals(channelUID.getId())) {
|
||||
if (ht != null && h != null) {
|
||||
if (command instanceof StringType) {
|
||||
StringType strCommand = (StringType) command;
|
||||
logger.debug("Changing override for hub {} to {}", channelUID, strCommand);
|
||||
try {
|
||||
OverrideMode mode = OverrideMode.getByName(strCommand.toFullString());
|
||||
ht.getConnection().setOverride(h, mode);
|
||||
} catch (NoboCommunicationException nce) {
|
||||
logger.debug("Failed setting override mode", nce);
|
||||
} catch (NoboDataException nde) {
|
||||
logger.debug("Date format error setting override mode", nde);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Command of wrong type: {} ({})", command, command.getClass().getName());
|
||||
}
|
||||
} else {
|
||||
if (null == h) {
|
||||
logger.debug("Could not set override, hub not detected yet");
|
||||
}
|
||||
|
||||
if (null == ht) {
|
||||
logger.debug("Could not set override, hub connection thread not set up yet");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
NoboHubBridgeConfiguration config = getConfigAs(NoboHubBridgeConfiguration.class);
|
||||
|
||||
String serialNumber = config.serialNumber;
|
||||
if (null == serialNumber) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/message.missing.serial");
|
||||
return;
|
||||
}
|
||||
|
||||
String hostName = config.hostName;
|
||||
if (null == hostName || hostName.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/message.bridge.missing.hostname");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Looking for Hub {} at {}", config.serialNumber, config.hostName);
|
||||
|
||||
// Set the thing status to UNKNOWN temporarily and let the background task decide for the real status.
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
// Background handshake:
|
||||
scheduler.execute(() -> {
|
||||
try {
|
||||
HubConnection conn = new HubConnection(hostName, serialNumber, this);
|
||||
conn.connect();
|
||||
|
||||
logger.debug("Done connecting to {} ({})", hostName, serialNumber);
|
||||
|
||||
Duration timeout = RECOMMENDED_KEEPALIVE_INTERVAL;
|
||||
if (config.pollingInterval > 0) {
|
||||
timeout = Duration.ofSeconds(config.pollingInterval);
|
||||
}
|
||||
|
||||
logger.debug("Starting communication thread to {}", hostName);
|
||||
|
||||
HubCommunicationThread ht = new HubCommunicationThread(conn, this, timeout);
|
||||
ht.start();
|
||||
hubThread = ht;
|
||||
|
||||
if (ht.getConnection().isConnected()) {
|
||||
logger.debug("Communication thread to {} is up and running, we are online", hostName);
|
||||
updateProperty(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
} else {
|
||||
logger.debug("HubCommunicationThread is not connected anymore, setting to OFFLINE");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/message.bridge.connection.failed");
|
||||
}
|
||||
} catch (NoboCommunicationException commEx) {
|
||||
logger.debug("HubCommunicationThread failed, exiting thread");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, commEx.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Disposing NoboHub '{}'", getThing().getUID().getId());
|
||||
|
||||
final NoboThingDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.stopScan();
|
||||
}
|
||||
|
||||
HubCommunicationThread ht = this.hubThread;
|
||||
if (ht != null) {
|
||||
logger.debug("Stopping communication thread");
|
||||
ht.stopNow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler handler, Thing thing) {
|
||||
logger.info("Adding thing: {}", thing.getLabel());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler handler, Thing thing) {
|
||||
logger.info("Disposing thing: {}", thing.getLabel());
|
||||
}
|
||||
|
||||
private void onUpdate(Hub hub) {
|
||||
logger.debug("Updating Hub: {}", hub.getName());
|
||||
this.hub = hub;
|
||||
OverridePlan activeOverridePlan = getOverride(hub.getActiveOverrideId());
|
||||
|
||||
if (null != activeOverridePlan) {
|
||||
logger.debug("Updating Hub with ActiveOverrideId {} with Name {}", activeOverridePlan.getId(),
|
||||
activeOverridePlan.getMode().name());
|
||||
|
||||
updateState(NoboHubBindingConstants.CHANNEL_HUB_ACTIVE_OVERRIDE_NAME,
|
||||
StringType.valueOf(activeOverridePlan.getMode().name()));
|
||||
}
|
||||
|
||||
// Update all zones to set online status and update profile name from weekProfileRegister
|
||||
for (Zone zone : zoneRegister.values()) {
|
||||
refreshZone(zone);
|
||||
}
|
||||
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_HOSTNAME, hub.getName());
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, hub.getSerialNumber().toString());
|
||||
properties.put(PROPERTY_SOFTWARE_VERSION, hub.getSoftwareVersion());
|
||||
properties.put(Thing.PROPERTY_HARDWARE_VERSION, hub.getHardwareVersion());
|
||||
properties.put(PROPERTY_PRODUCTION_DATE, hub.getProductionDate());
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
public void receivedData(@Nullable String line) {
|
||||
try {
|
||||
parseLine(line);
|
||||
} catch (NoboDataException nde) {
|
||||
logger.debug("Failed parsing line '{}': {}", line, nde.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseLine(@Nullable String line) throws NoboDataException {
|
||||
if (null == line) {
|
||||
return;
|
||||
}
|
||||
|
||||
NoboThingDiscoveryService ds = this.discoveryService;
|
||||
if (line.startsWith("H01")) {
|
||||
Zone zone = Zone.fromH01(line);
|
||||
zoneRegister.put(zone);
|
||||
if (null != ds) {
|
||||
ds.detectZones(zoneRegister.values());
|
||||
}
|
||||
} else if (line.startsWith("H02")) {
|
||||
Component component = Component.fromH02(line);
|
||||
componentRegister.put(component);
|
||||
if (null != ds) {
|
||||
ds.detectComponents(componentRegister.values());
|
||||
}
|
||||
} else if (line.startsWith("H03")) {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(line);
|
||||
weekProfileRegister.put(weekProfile);
|
||||
} else if (line.startsWith("H04")) {
|
||||
OverridePlan overridePlan = OverridePlan.fromH04(line);
|
||||
overrideRegister.put(overridePlan);
|
||||
} else if (line.startsWith("H05")) {
|
||||
Hub hub = Hub.fromH05(line);
|
||||
onUpdate(hub);
|
||||
} else if (line.startsWith("S00")) {
|
||||
Zone zone = Zone.fromH01(line);
|
||||
zoneRegister.remove(zone.getId());
|
||||
} else if (line.startsWith("S01")) {
|
||||
Component component = Component.fromH02(line);
|
||||
componentRegister.remove(component.getSerialNumber());
|
||||
} else if (line.startsWith("S02")) {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(line);
|
||||
weekProfileRegister.remove(weekProfile.getId());
|
||||
} else if (line.startsWith("S03")) {
|
||||
OverridePlan overridePlan = OverridePlan.fromH04(line);
|
||||
overrideRegister.remove(overridePlan.getId());
|
||||
} else if (line.startsWith("B00")) {
|
||||
Zone zone = Zone.fromH01(line);
|
||||
zoneRegister.put(zone);
|
||||
if (null != ds) {
|
||||
ds.detectZones(zoneRegister.values());
|
||||
}
|
||||
} else if (line.startsWith("B01")) {
|
||||
Component component = Component.fromH02(line);
|
||||
componentRegister.put(component);
|
||||
if (null != ds) {
|
||||
ds.detectComponents(componentRegister.values());
|
||||
}
|
||||
} else if (line.startsWith("B02")) {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(line);
|
||||
weekProfileRegister.put(weekProfile);
|
||||
} else if (line.startsWith("B03")) {
|
||||
OverridePlan overridePlan = OverridePlan.fromH04(line);
|
||||
overrideRegister.put(overridePlan);
|
||||
} else if (line.startsWith("V00")) {
|
||||
Zone zone = Zone.fromH01(line);
|
||||
zoneRegister.put(zone);
|
||||
refreshZone(zone);
|
||||
} else if (line.startsWith("V01")) {
|
||||
Component component = Component.fromH02(line);
|
||||
componentRegister.put(component);
|
||||
refreshComponent(component);
|
||||
} else if (line.startsWith("V02")) {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(line);
|
||||
weekProfileRegister.put(weekProfile);
|
||||
} else if (line.startsWith("V03")) {
|
||||
Hub hub = Hub.fromH05(line);
|
||||
onUpdate(hub);
|
||||
} else if (line.startsWith("Y02")) {
|
||||
Temperature temp = Temperature.fromY02(line);
|
||||
Component component = getComponent(temp.getSerialNumber());
|
||||
if (null != component) {
|
||||
component.setTemperature(temp.getTemperature());
|
||||
refreshComponent(component);
|
||||
int zoneId = component.getTemperatureSensorForZoneId();
|
||||
if (zoneId >= 0) {
|
||||
Zone zone = getZone(zoneId);
|
||||
if (null != zone) {
|
||||
zone.setTemperature(temp.getTemperature());
|
||||
refreshZone(zone);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (line.startsWith("E00")) {
|
||||
logger.debug("Error from Hub: {}", line);
|
||||
} else {
|
||||
// HANDSHAKE: Basic part of keepalive
|
||||
// V06: Encryption key
|
||||
// H00: contains no information
|
||||
if (!line.startsWith("HANDSHAKE") && !line.startsWith("V06") && !line.startsWith("H00")) {
|
||||
logger.info("Unknown information from Hub: '{}}'", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Nullable Zone getZone(Integer id) {
|
||||
return zoneRegister.get(id);
|
||||
}
|
||||
|
||||
public @Nullable WeekProfile getWeekProfile(Integer id) {
|
||||
return weekProfileRegister.get(id);
|
||||
}
|
||||
|
||||
public @Nullable Component getComponent(SerialNumber serialNumber) {
|
||||
return componentRegister.get(serialNumber);
|
||||
}
|
||||
|
||||
public @Nullable OverridePlan getOverride(Integer id) {
|
||||
return overrideRegister.get(id);
|
||||
}
|
||||
|
||||
public void sendCommand(String command) {
|
||||
@Nullable
|
||||
HubCommunicationThread ht = this.hubThread;
|
||||
if (ht != null) {
|
||||
HubConnection conn = ht.getConnection();
|
||||
conn.sendCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshZone(Zone zone) {
|
||||
this.getThing().getThings().forEach(thing -> {
|
||||
if (thing.getHandler() instanceof ZoneHandler) {
|
||||
ZoneHandler handler = (ZoneHandler) thing.getHandler();
|
||||
if (handler != null && handler.getZoneId() == zone.getId()) {
|
||||
handler.onUpdate(zone);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshComponent(Component component) {
|
||||
this.getThing().getThings().forEach(thing -> {
|
||||
if (thing.getHandler() instanceof ComponentHandler) {
|
||||
ComponentHandler handler = (ComponentHandler) thing.getHandler();
|
||||
if (handler != null) {
|
||||
SerialNumber handlerSerial = handler.getSerialNumber();
|
||||
if (handlerSerial != null && component.getSerialNumber().equals(handlerSerial)) {
|
||||
handler.onUpdate(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void startScan() {
|
||||
try {
|
||||
@Nullable
|
||||
HubCommunicationThread ht = this.hubThread;
|
||||
if (ht != null) {
|
||||
ht.getConnection().refreshAll();
|
||||
}
|
||||
} catch (NoboCommunicationException noboEx) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"@text/message.bridge.status.failed [\"" + noboEx.getMessage() + "\"]");
|
||||
}
|
||||
}
|
||||
|
||||
public void setDicsoveryService(NoboThingDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
public Collection<WeekProfile> getWeekProfiles() {
|
||||
return weekProfileRegister.values();
|
||||
}
|
||||
|
||||
public void setStatusInfo(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
|
||||
updateStatus(status, statusDetail, description);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link NoboHubConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboHubConfiguration {
|
||||
|
||||
/**
|
||||
* Serial number of Nobø Hub.
|
||||
*/
|
||||
@Nullable
|
||||
public String serialNumber;
|
||||
|
||||
/**
|
||||
* Host address of Nobø Hub.
|
||||
*/
|
||||
@Nullable
|
||||
public String hostName;
|
||||
|
||||
/**
|
||||
* Polling interval (seconds)
|
||||
*/
|
||||
public int pollingInterval;
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_COMPONENT;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_HUB;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_ZONE;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.discovery.NoboThingDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.framework.ServiceRegistration;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link NoboHubHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.nobohub", service = ThingHandlerFactory.class)
|
||||
public class NoboHubHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NoboHubHandlerFactory.class);
|
||||
private final Map<ThingTypeUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPES_UIDS = new HashSet<>(List.of(THING_TYPE_HUB));
|
||||
private @NonNullByDefault({}) WeekProfileStateDescriptionOptionsProvider stateDescriptionOptionsProvider;
|
||||
|
||||
private final NoboHubTranslationProvider i18nProvider;
|
||||
|
||||
@Activate
|
||||
public NoboHubHandlerFactory(
|
||||
final @Reference WeekProfileStateDescriptionOptionsProvider stateDescriptionOptionsProvider,
|
||||
final @Reference NoboHubTranslationProvider i18nProvider) {
|
||||
this.stateDescriptionOptionsProvider = stateDescriptionOptionsProvider;
|
||||
this.i18nProvider = i18nProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_HUB.equals(thingTypeUID)) {
|
||||
NoboHubBridgeHandler handler = new NoboHubBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(handler);
|
||||
return handler;
|
||||
} else if (THING_TYPE_ZONE.equals(thingTypeUID)) {
|
||||
logger.debug("Setting WeekProfileStateDescriptionOptionsProvider for: {}", thing.getLabel());
|
||||
return new ZoneHandler(thing, i18nProvider, stateDescriptionOptionsProvider);
|
||||
} else if (THING_TYPE_COMPONENT.equals(thingTypeUID)) {
|
||||
return new ComponentHandler(thing, i18nProvider);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void removeHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof NoboHubBridgeHandler) {
|
||||
unregisterDiscoveryService((NoboHubBridgeHandler) thingHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void registerDiscoveryService(NoboHubBridgeHandler bridgeHandler) {
|
||||
NoboThingDiscoveryService discoveryService = new NoboThingDiscoveryService(bridgeHandler);
|
||||
bridgeHandler.setDicsoveryService(discoveryService);
|
||||
this.discoveryServiceRegs.put(bridgeHandler.getThing().getThingTypeUID(), getBundleContext()
|
||||
.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
|
||||
}
|
||||
|
||||
private synchronized void unregisterDiscoveryService(NoboHubBridgeHandler bridgeHandler) {
|
||||
try {
|
||||
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs
|
||||
.remove(bridgeHandler.getThing().getThingTypeUID());
|
||||
if (null != serviceReg) {
|
||||
NoboThingDiscoveryService service = (NoboThingDiscoveryService) getBundleContext()
|
||||
.getService(serviceReg.getReference());
|
||||
serviceReg.unregister();
|
||||
if (null != service) {
|
||||
service.deactivate();
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
logger.debug("Failed to unregister service", iae);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference
|
||||
protected void setDynamicStateDescriptionProvider(WeekProfileStateDescriptionOptionsProvider provider) {
|
||||
this.stateDescriptionOptionsProvider = provider;
|
||||
}
|
||||
|
||||
protected void unsetDynamicStateDescriptionProvider(WeekProfileStateDescriptionOptionsProvider provider) {
|
||||
this.stateDescriptionOptionsProvider = null;
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* This class provides translated texts
|
||||
*
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = NoboHubTranslationProvider.class)
|
||||
public class NoboHubTranslationProvider {
|
||||
|
||||
private final Bundle bundle;
|
||||
private final TranslationProvider i18nProvider;
|
||||
private final LocaleProvider localeProvider;
|
||||
|
||||
@Activate
|
||||
public NoboHubTranslationProvider(@Reference TranslationProvider i18nProvider,
|
||||
@Reference LocaleProvider localeProvider) {
|
||||
this.bundle = FrameworkUtil.getBundle(this.getClass());
|
||||
this.i18nProvider = i18nProvider;
|
||||
this.localeProvider = localeProvider;
|
||||
}
|
||||
|
||||
public NoboHubTranslationProvider(final NoboHubTranslationProvider other) {
|
||||
this.bundle = other.bundle;
|
||||
this.i18nProvider = other.i18nProvider;
|
||||
this.localeProvider = other.localeProvider;
|
||||
}
|
||||
|
||||
public String getText(String key, @Nullable Object... arguments) {
|
||||
Locale locale = localeProvider.getLocale();
|
||||
String message = i18nProvider.getText(bundle, key, this.getDefaultText(key), locale, arguments);
|
||||
if (message != null) {
|
||||
return message;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
public @Nullable String getDefaultText(String key) {
|
||||
return i18nProvider.getText(bundle, key, key, Locale.ENGLISH);
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of week profile state options.
|
||||
*
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicStateDescriptionProvider.class, WeekProfileStateDescriptionOptionsProvider.class })
|
||||
@NonNullByDefault
|
||||
public class WeekProfileStateDescriptionOptionsProvider extends BaseDynamicStateDescriptionProvider {
|
||||
|
||||
@Activate
|
||||
public WeekProfileStateDescriptionOptionsProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ZoneConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneConfiguration {
|
||||
|
||||
/**
|
||||
* Id of the zone
|
||||
*/
|
||||
public int id;
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_ACTIVE_WEEK_PROFILE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_ACTIVE_WEEK_PROFILE_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_CALCULATED_WEEK_PROFILE_STATUS;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_COMFORT_TEMPERATURE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_CURRENT_TEMPERATURE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.CHANNEL_ZONE_ECO_TEMPERATURE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_HOSTNAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_ZONE_ID;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboDataException;
|
||||
import org.openhab.binding.nobohub.internal.model.WeekProfile;
|
||||
import org.openhab.binding.nobohub.internal.model.WeekProfileStatus;
|
||||
import org.openhab.binding.nobohub.internal.model.Zone;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.StateOption;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Shows information about a named Zone in the Nobø Hub.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ZoneHandler.class);
|
||||
|
||||
private final WeekProfileStateDescriptionOptionsProvider weekProfileStateDescriptionOptionsProvider;
|
||||
|
||||
private final NoboHubTranslationProvider messages;
|
||||
|
||||
protected int id;
|
||||
|
||||
public ZoneHandler(Thing thing, NoboHubTranslationProvider messages,
|
||||
WeekProfileStateDescriptionOptionsProvider weekProfileStateDescriptionOptionsProvider) {
|
||||
super(thing);
|
||||
this.messages = messages;
|
||||
this.weekProfileStateDescriptionOptionsProvider = weekProfileStateDescriptionOptionsProvider;
|
||||
}
|
||||
|
||||
public void onUpdate(Zone zone) {
|
||||
logger.debug("Updating zone: {}", zone.getName());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
QuantityType<Temperature> comfortTemperature = new QuantityType<>(zone.getComfortTemperature(),
|
||||
SIUnits.CELSIUS);
|
||||
updateState(CHANNEL_ZONE_COMFORT_TEMPERATURE, comfortTemperature);
|
||||
QuantityType<Temperature> ecoTemperature = new QuantityType<>(zone.getEcoTemperature(), SIUnits.CELSIUS);
|
||||
updateState(CHANNEL_ZONE_ECO_TEMPERATURE, ecoTemperature);
|
||||
|
||||
Double temp = zone.getTemperature();
|
||||
if (temp != null && !Double.isNaN(temp)) {
|
||||
QuantityType<Temperature> currentTemperature = new QuantityType<>(temp, SIUnits.CELSIUS);
|
||||
updateState(CHANNEL_ZONE_CURRENT_TEMPERATURE, currentTemperature);
|
||||
}
|
||||
|
||||
int activeWeekProfileId = zone.getActiveWeekProfileId();
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
logger.debug("Updating zone: {} at hub bridge: {}", zone.getName(),
|
||||
noboHub.getStatusInfo().getStatus().name());
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
if (hubHandler != null) {
|
||||
WeekProfile weekProfile = hubHandler.getWeekProfile(activeWeekProfileId);
|
||||
if (null != weekProfile) {
|
||||
updateState(CHANNEL_ZONE_ACTIVE_WEEK_PROFILE_NAME, StringType.valueOf(weekProfile.getName()));
|
||||
updateState(CHANNEL_ZONE_ACTIVE_WEEK_PROFILE,
|
||||
DecimalType.valueOf(String.valueOf(weekProfile.getId())));
|
||||
try {
|
||||
WeekProfileStatus weekProfileStatus = weekProfile.getStatusAt(LocalDateTime.now());
|
||||
updateState(CHANNEL_ZONE_CALCULATED_WEEK_PROFILE_STATUS,
|
||||
StringType.valueOf(weekProfileStatus.name()));
|
||||
} catch (NoboDataException nde) {
|
||||
logger.debug("Failed getting current week profile status", nde);
|
||||
}
|
||||
}
|
||||
|
||||
List<StateOption> options = new ArrayList<>();
|
||||
logger.debug("Updating week profile state description options for zone {}.", zone.getName());
|
||||
for (WeekProfile wp : hubHandler.getWeekProfiles()) {
|
||||
options.add(new StateOption(String.valueOf(wp.getId()), wp.getName()));
|
||||
}
|
||||
logger.debug("State options count: {}. First: {}", options.size(),
|
||||
(!options.isEmpty()) ? options.get(0) : 0);
|
||||
weekProfileStateDescriptionOptionsProvider.setStateOptions(
|
||||
new ChannelUID(getThing().getUID(), CHANNEL_ZONE_ACTIVE_WEEK_PROFILE), options);
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(PROPERTY_HOSTNAME, zone.getName());
|
||||
properties.put(PROPERTY_ZONE_ID, Integer.toString(zone.getId()));
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
this.id = getConfigAs(ZoneConfiguration.class).id;
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
logger.debug("Refreshing channel {}", channelUID);
|
||||
|
||||
Zone zone = getZone();
|
||||
if (null == zone) {
|
||||
logger.debug("Could not find Zone with id {} for channel {}", id, channelUID);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE,
|
||||
messages.getText("message.zone.notfound", id, channelUID));
|
||||
} else {
|
||||
onUpdate(zone);
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
if (null != hubHandler) {
|
||||
WeekProfile weekProfile = hubHandler.getWeekProfile(zone.getActiveWeekProfileId());
|
||||
if (null != weekProfile) {
|
||||
String weekProfileName = weekProfile.getName();
|
||||
StringType weekProfileValue = StringType.valueOf(weekProfileName);
|
||||
updateState(CHANNEL_ZONE_ACTIVE_WEEK_PROFILE_NAME, weekProfileValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHANNEL_ZONE_COMFORT_TEMPERATURE.equals(channelUID.getId())) {
|
||||
Zone zone = getZone();
|
||||
if (zone != null) {
|
||||
if (command instanceof DecimalType) {
|
||||
DecimalType comfortTemp = (DecimalType) command;
|
||||
logger.debug("Set comfort temp for zone {} to {}", zone.getName(), comfortTemp.doubleValue());
|
||||
zone.setComfortTemperature(comfortTemp.intValue());
|
||||
sendCommand(zone.generateCommandString("U00"));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHANNEL_ZONE_ECO_TEMPERATURE.equals(channelUID.getId())) {
|
||||
Zone zone = getZone();
|
||||
if (zone != null) {
|
||||
if (command instanceof DecimalType) {
|
||||
DecimalType ecoTemp = (DecimalType) command;
|
||||
logger.debug("Set eco temp for zone {} to {}", zone.getName(), ecoTemp.doubleValue());
|
||||
zone.setEcoTemperature(ecoTemp.intValue());
|
||||
sendCommand(zone.generateCommandString("U00"));
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHANNEL_ZONE_ACTIVE_WEEK_PROFILE.equals(channelUID.getId())) {
|
||||
Zone zone = getZone();
|
||||
if (zone != null) {
|
||||
if (command instanceof DecimalType) {
|
||||
DecimalType weekProfileId = (DecimalType) command;
|
||||
logger.debug("Set week profile for zone {} to {}", zone.getName(), weekProfileId);
|
||||
zone.setWeekProfile(weekProfileId.intValue());
|
||||
sendCommand(zone.generateCommandString("U00"));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Unhandled zone command {}: {}", channelUID.getId(), command);
|
||||
}
|
||||
|
||||
public @Nullable Integer getZoneId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void sendCommand(String command) {
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
if (null != hubHandler) {
|
||||
hubHandler.sendCommand(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Zone getZone() {
|
||||
Bridge noboHub = getBridge();
|
||||
if (null != noboHub) {
|
||||
NoboHubBridgeHandler hubHandler = (NoboHubBridgeHandler) noboHub.getHandler();
|
||||
if (null != hubHandler) {
|
||||
return hubHandler.getZone(id);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.connection;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBindingConstants;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBridgeHandler;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboCommunicationException;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Thread that reads from the Nobø Hub and sends HANDSHAKEs to keep the connection open.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HubCommunicationThread extends Thread {
|
||||
|
||||
private enum HubCommunicationThreadState {
|
||||
STARTING(null, null, ""),
|
||||
CONNECTED(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""),
|
||||
DISCONNECTED(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/message.bridge.status.failed"),
|
||||
STOPPED(null, null, "");
|
||||
|
||||
private final @Nullable ThingStatus status;
|
||||
private final @Nullable ThingStatusDetail statusDetail;
|
||||
private final String errorMessage;
|
||||
|
||||
HubCommunicationThreadState(@Nullable ThingStatus status, @Nullable ThingStatusDetail statusDetail,
|
||||
String errorMessage) {
|
||||
this.status = status;
|
||||
this.statusDetail = statusDetail;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
public @Nullable ThingStatus getThingStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public @Nullable ThingStatusDetail getThingStatusDetail() {
|
||||
return statusDetail;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HubCommunicationThread.class);
|
||||
|
||||
private final HubConnection hubConnection;
|
||||
private final NoboHubBridgeHandler hubHandler;
|
||||
private final Duration timeout;
|
||||
private Instant lastTimeFullScan;
|
||||
|
||||
private volatile boolean stopped = false;
|
||||
private HubCommunicationThreadState currentState = HubCommunicationThreadState.STARTING;
|
||||
|
||||
public HubCommunicationThread(HubConnection hubConnection, NoboHubBridgeHandler hubHandler, Duration timeout) {
|
||||
this.hubConnection = hubConnection;
|
||||
this.hubHandler = hubHandler;
|
||||
this.timeout = timeout;
|
||||
this.lastTimeFullScan = Instant.now();
|
||||
}
|
||||
|
||||
public void stopNow() {
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!stopped) {
|
||||
switch (currentState) {
|
||||
case STARTING:
|
||||
try {
|
||||
hubConnection.refreshAll();
|
||||
lastTimeFullScan = Instant.now();
|
||||
setNextState(HubCommunicationThreadState.CONNECTED);
|
||||
} catch (NoboCommunicationException nce) {
|
||||
logger.debug("Communication error with Hub", nce);
|
||||
setNextState(HubCommunicationThreadState.DISCONNECTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case CONNECTED:
|
||||
try {
|
||||
if (hubConnection.hasData()) {
|
||||
hubConnection.processReads(timeout);
|
||||
}
|
||||
|
||||
if (Instant.now()
|
||||
.isAfter(lastTimeFullScan.plus(NoboHubBindingConstants.TIME_BETWEEN_FULL_SCANS))) {
|
||||
hubConnection.refreshAll();
|
||||
lastTimeFullScan = Instant.now();
|
||||
} else {
|
||||
hubConnection.handshake();
|
||||
}
|
||||
|
||||
hubConnection.processReads(timeout);
|
||||
} catch (NoboCommunicationException nce) {
|
||||
logger.debug("Communication error with Hub", nce);
|
||||
setNextState(HubCommunicationThreadState.DISCONNECTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case DISCONNECTED:
|
||||
try {
|
||||
Thread.sleep(NoboHubBindingConstants.TIME_BETWEEN_RETRIES_ON_ERROR.toMillis());
|
||||
try {
|
||||
logger.debug("Trying to do a hard reconnect");
|
||||
hubConnection.hardReconnect();
|
||||
setNextState(HubCommunicationThreadState.CONNECTED);
|
||||
} catch (NoboCommunicationException nce2) {
|
||||
logger.debug("Failed to reconnect connection", nce2);
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
logger.debug("Interrupted from sleep after error");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
break;
|
||||
|
||||
case STOPPED:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stopped) {
|
||||
logger.debug("HubCommunicationThread is stopped, disconnecting from Hub");
|
||||
setNextState(HubCommunicationThreadState.STOPPED);
|
||||
try {
|
||||
hubConnection.disconnect();
|
||||
} catch (NoboCommunicationException nce) {
|
||||
logger.debug("Error disconnecting from Hub", nce);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HubConnection getConnection() {
|
||||
return hubConnection;
|
||||
}
|
||||
|
||||
private void setNextState(HubCommunicationThreadState newState) {
|
||||
currentState = newState;
|
||||
ThingStatus stateThingStatus = newState.getThingStatus();
|
||||
ThingStatusDetail stateThingStatusDetail = newState.getThingStatusDetail();
|
||||
if (null != stateThingStatus && null != stateThingStatusDetail) {
|
||||
hubHandler.setStatusInfo(stateThingStatus, stateThingStatusDetail, newState.getErrorMessage());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.connection;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.Helpers;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBindingConstants;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBridgeHandler;
|
||||
import org.openhab.binding.nobohub.internal.model.Hub;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboCommunicationException;
|
||||
import org.openhab.binding.nobohub.internal.model.NoboDataException;
|
||||
import org.openhab.binding.nobohub.internal.model.OverrideMode;
|
||||
import org.openhab.binding.nobohub.internal.model.OverridePlan;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Connection to the Nobø Hub (Socket wrapper).
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HubConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(HubConnection.class);
|
||||
|
||||
private final String hostName;
|
||||
private final NoboHubBridgeHandler hubHandler;
|
||||
private final String serialNumber;
|
||||
|
||||
private @Nullable InetAddress host;
|
||||
private @Nullable Socket hubConnection;
|
||||
private @Nullable PrintWriter out;
|
||||
private @Nullable BufferedReader in;
|
||||
|
||||
public HubConnection(String hostName, String serialNumber, NoboHubBridgeHandler hubHandler)
|
||||
throws NoboCommunicationException {
|
||||
this.hostName = hostName;
|
||||
this.serialNumber = serialNumber;
|
||||
this.hubHandler = hubHandler;
|
||||
}
|
||||
|
||||
public void connect() throws NoboCommunicationException {
|
||||
connectSocket();
|
||||
|
||||
String hello = String.format("HELLO %s %s %s\r", NoboHubBindingConstants.API_VERSION, serialNumber,
|
||||
getDateString());
|
||||
write(hello);
|
||||
|
||||
String helloRes = readLine();
|
||||
if (null == helloRes || !helloRes.startsWith("HELLO")) {
|
||||
if (helloRes != null && helloRes.startsWith("REJECT")) {
|
||||
String[] reject = helloRes.split(" ", 2);
|
||||
throw new NoboCommunicationException(String.format("Hub rejects us with reason %s: %s", reject[1],
|
||||
NoboHubBindingConstants.REJECT_REASONS.get(reject[1])));
|
||||
} else {
|
||||
throw new NoboCommunicationException("Hub rejects us with unknown reason");
|
||||
}
|
||||
}
|
||||
|
||||
write("HANDSHAKE\r");
|
||||
|
||||
String handshakeRes = readLine();
|
||||
if (null == handshakeRes || !handshakeRes.startsWith("HANDSHAKE")) {
|
||||
throw new NoboCommunicationException("Hub rejects handshake");
|
||||
}
|
||||
|
||||
refreshAllNoReconnect();
|
||||
}
|
||||
|
||||
public void handshake() throws NoboCommunicationException {
|
||||
if (!isConnected()) {
|
||||
connect();
|
||||
} else {
|
||||
write("HANDSHAKE\r");
|
||||
}
|
||||
}
|
||||
|
||||
public void setOverride(Hub hub, OverrideMode nextMode) throws NoboDataException, NoboCommunicationException {
|
||||
if (!isConnected()) {
|
||||
connect();
|
||||
}
|
||||
|
||||
OverridePlan overridePlan = OverridePlan.fromMode(nextMode, LocalDateTime.now());
|
||||
sendCommand(overridePlan.generateCommandString("A03"));
|
||||
|
||||
String line = "";
|
||||
while (line != null && !line.startsWith("B03")) {
|
||||
line = readLine();
|
||||
hubHandler.receivedData(line);
|
||||
}
|
||||
|
||||
String l = line;
|
||||
if (null != l) {
|
||||
OverridePlan newOverridePlan = OverridePlan.fromH04(l);
|
||||
hub.setActiveOverrideId(newOverridePlan.getId());
|
||||
sendCommand(hub.generateCommandString("U03"));
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshAll() throws NoboCommunicationException {
|
||||
if (!isConnected()) {
|
||||
connect();
|
||||
} else {
|
||||
refreshAllNoReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshAllNoReconnect() throws NoboCommunicationException {
|
||||
write("G00\r");
|
||||
|
||||
String line = "";
|
||||
while (line != null && !line.startsWith("H05")) {
|
||||
line = readLine();
|
||||
hubHandler.receivedData(line);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
Socket conn = this.hubConnection;
|
||||
if (null != conn) {
|
||||
return conn.isConnected();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasData() throws NoboCommunicationException {
|
||||
BufferedReader i = this.in;
|
||||
if (null != i) {
|
||||
try {
|
||||
return i.ready();
|
||||
} catch (IOException ioex) {
|
||||
throw new NoboCommunicationException("Failed detecting if buffer has any data", ioex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void processReads(Duration timeout) throws NoboCommunicationException {
|
||||
try {
|
||||
Socket conn = this.hubConnection;
|
||||
if (null == conn) {
|
||||
throw new NoboCommunicationException("No connection to Hub");
|
||||
}
|
||||
|
||||
logger.trace("Reading from Hub, waiting maximum {}", Helpers.formatDuration(timeout));
|
||||
conn.setSoTimeout((int) timeout.toMillis());
|
||||
|
||||
try {
|
||||
String line = readLine();
|
||||
if (line != null && line.startsWith("HANDSHAKE")) {
|
||||
line = readLine();
|
||||
}
|
||||
|
||||
hubHandler.receivedData(line);
|
||||
} catch (NoboCommunicationException nce) {
|
||||
if (!(nce.getCause() instanceof SocketTimeoutException)) {
|
||||
connectSocket();
|
||||
}
|
||||
}
|
||||
} catch (SocketException se) {
|
||||
throw new NoboCommunicationException("Failed setting read timeout", se);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String readLine() throws NoboCommunicationException {
|
||||
BufferedReader reader = this.in;
|
||||
try {
|
||||
if (null != reader) {
|
||||
String line = reader.readLine();
|
||||
if (line != null) {
|
||||
logger.trace("Reading raw data string from Nobø Hub: {}", line);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
} catch (IOException ioex) {
|
||||
throw new NoboCommunicationException("Failed reading from Nobø Hub", ioex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void sendCommand(String command) {
|
||||
write(command);
|
||||
}
|
||||
|
||||
private void write(String s) {
|
||||
@Nullable
|
||||
PrintWriter o = this.out;
|
||||
if (null != o) {
|
||||
logger.trace("Sending '{}'", s);
|
||||
o.write(s);
|
||||
o.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void connectSocket() throws NoboCommunicationException {
|
||||
if (null == host) {
|
||||
try {
|
||||
host = InetAddress.getByName(hostName);
|
||||
} catch (IOException ioex) {
|
||||
throw new NoboCommunicationException(String.format("Failed to resolve IP address of %s", hostName),
|
||||
ioex);
|
||||
}
|
||||
}
|
||||
try {
|
||||
Socket conn = new Socket(host, NoboHubBindingConstants.NOBO_HUB_TCP_PORT);
|
||||
out = new PrintWriter(conn.getOutputStream(), true);
|
||||
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
hubConnection = conn;
|
||||
} catch (IOException ioex) {
|
||||
throw new NoboCommunicationException(String.format("Failed connecting to Nobø Hub at %s", hostName), ioex);
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() throws NoboCommunicationException {
|
||||
try {
|
||||
PrintWriter o = this.out;
|
||||
if (o != null) {
|
||||
o.close();
|
||||
}
|
||||
|
||||
BufferedReader i = this.in;
|
||||
if (i != null) {
|
||||
i.close();
|
||||
}
|
||||
|
||||
Socket conn = this.hubConnection;
|
||||
if (conn != null) {
|
||||
conn.close();
|
||||
}
|
||||
} catch (IOException ioex) {
|
||||
throw new NoboCommunicationException("Error disconnecting from Hub", ioex);
|
||||
}
|
||||
}
|
||||
|
||||
public void hardReconnect() throws NoboCommunicationException {
|
||||
disconnect();
|
||||
connect();
|
||||
}
|
||||
|
||||
private String getDateString() {
|
||||
return LocalDateTime.now().format(NoboHubBindingConstants.DATE_FORMAT_SECONDS);
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.NOBO_HUB_BROADCAST_ADDRESS;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.NOBO_HUB_BROADCAST_PORT;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.NOBO_HUB_MULTICAST_PORT;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_HOSTNAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_VENDOR_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_HUB;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubHandlerFactory.DISCOVERABLE_DEVICE_TYPES_UIDS;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.MulticastSocket;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class identifies devices that are available on the Nobø hub and adds discovery results for them.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.nobohub")
|
||||
public class NoboHubDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
|
||||
private final Logger logger = LoggerFactory.getLogger(NoboHubDiscoveryService.class);
|
||||
|
||||
private @NonNullByDefault({}) NoboHubBridgeHandler hubBridgeHandler;
|
||||
|
||||
public NoboHubDiscoveryService() {
|
||||
super(DISCOVERABLE_DEVICE_TYPES_UIDS, 10, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
scheduler.execute(scanner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
removeOlderResults(new Date().getTime());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler thingHandler) {
|
||||
if (thingHandler instanceof NoboHubBridgeHandler) {
|
||||
this.hubBridgeHandler = (NoboHubBridgeHandler) thingHandler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return hubBridgeHandler;
|
||||
}
|
||||
|
||||
private final Runnable scanner = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean found = false;
|
||||
logger.info("Detecting Glen Dimplex Nobø Hubs, trying Multicast");
|
||||
try {
|
||||
MulticastSocket socket = new MulticastSocket(NOBO_HUB_MULTICAST_PORT);
|
||||
found = waitOnSocket(socket, "multicast");
|
||||
} catch (IOException ioex) {
|
||||
logger.error("Failed detecting Nobø Hub via multicast", ioex);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
logger.debug("Detecting Glen Dimplex Nobø Hubs, trying Broadcast");
|
||||
|
||||
try {
|
||||
DatagramSocket socket = new DatagramSocket(NOBO_HUB_BROADCAST_PORT,
|
||||
InetAddress.getByName(NOBO_HUB_BROADCAST_ADDRESS));
|
||||
found = waitOnSocket(socket, "broadcast");
|
||||
} catch (IOException ioex) {
|
||||
logger.error("Failed detecting Nobø Hub via multicast, will try with Broadcast", ioex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean waitOnSocket(DatagramSocket socket, String type) throws IOException {
|
||||
try (socket) {
|
||||
socket.setBroadcast(true);
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
DatagramPacket data = new DatagramPacket(buffer, buffer.length);
|
||||
String received = "";
|
||||
while (!received.startsWith("__NOBOHUB__")) {
|
||||
socket.setSoTimeout((int) Duration.ofSeconds(4).toMillis());
|
||||
socket.receive(data);
|
||||
received = new String(buffer, 0, data.getLength());
|
||||
}
|
||||
|
||||
logger.debug("Hub detection using {}: Received: {} from {}", type, received, data.getAddress());
|
||||
|
||||
String[] parts = received.split("__", 3);
|
||||
if (3 != parts.length) {
|
||||
logger.debug("Data error, didn't contain three parts: '{}''", String.join("','", parts));
|
||||
return false;
|
||||
}
|
||||
|
||||
String serialNumberStart = parts[parts.length - 1];
|
||||
addDevice(serialNumberStart, data.getAddress().getHostName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void addDevice(String serialNumberStart, String hostName) {
|
||||
ThingUID bridge = new ThingUID(THING_TYPE_HUB, serialNumberStart);
|
||||
String label = "Nobø Hub " + serialNumberStart;
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(4);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumberStart);
|
||||
properties.put(PROPERTY_NAME, label);
|
||||
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
|
||||
properties.put(PROPERTY_HOSTNAME, hostName);
|
||||
|
||||
logger.debug("Adding device {} to inbox: {} {} at {}", bridge, label, serialNumberStart, hostName);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(bridge).withLabel(label)
|
||||
.withProperties(properties).withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.AUTODISCOVERED_THING_TYPES_UIDS;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_MODEL;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_TEMPERATURE_SENSOR_FOR_ZONE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_VENDOR_NAME;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_ZONE;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.PROPERTY_ZONE_ID;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_COMPONENT;
|
||||
import static org.openhab.binding.nobohub.internal.NoboHubBindingConstants.THING_TYPE_ZONE;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBridgeHandler;
|
||||
import org.openhab.binding.nobohub.internal.model.Component;
|
||||
import org.openhab.binding.nobohub.internal.model.Zone;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class identifies devices that are available on the Nobø hub and adds discovery results for them.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboThingDiscoveryService extends AbstractDiscoveryService {
|
||||
private final Logger logger = LoggerFactory.getLogger(NoboThingDiscoveryService.class);
|
||||
|
||||
private final NoboHubBridgeHandler bridgeHandler;
|
||||
|
||||
public NoboThingDiscoveryService(NoboHubBridgeHandler bridgeHandler) {
|
||||
super(AUTODISCOVERED_THING_TYPES_UIDS, 10, true);
|
||||
this.bridgeHandler = bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
bridgeHandler.startScan();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void stopScan() {
|
||||
super.stopScan();
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
removeOlderResults(new Date().getTime());
|
||||
}
|
||||
|
||||
public void detectZones(Collection<Zone> zones) {
|
||||
ThingUID bridge = bridgeHandler.getThing().getUID();
|
||||
List<Thing> things = bridgeHandler.getThing().getThings();
|
||||
|
||||
for (Zone zone : zones) {
|
||||
ThingUID discoveredThingId = new ThingUID(THING_TYPE_ZONE, bridge, Integer.toString(zone.getId()));
|
||||
|
||||
boolean addDiscoveredZone = true;
|
||||
for (Thing thing : things) {
|
||||
if (thing.getUID().equals(discoveredThingId)) {
|
||||
addDiscoveredZone = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (addDiscoveredZone) {
|
||||
String label = zone.getName();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(3);
|
||||
properties.put(PROPERTY_ZONE_ID, Integer.toString(zone.getId()));
|
||||
properties.put(PROPERTY_NAME, zone.getName());
|
||||
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
|
||||
|
||||
logger.debug("Adding device {} to inbox", discoveredThingId);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(discoveredThingId).withBridge(bridge)
|
||||
.withLabel(label).withProperties(properties).withRepresentationProperty("id").build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void detectComponents(Collection<Component> components) {
|
||||
ThingUID bridge = bridgeHandler.getThing().getUID();
|
||||
List<Thing> things = bridgeHandler.getThing().getThings();
|
||||
|
||||
for (Component component : components) {
|
||||
ThingUID discoveredThingId = new ThingUID(THING_TYPE_COMPONENT, bridge,
|
||||
component.getSerialNumber().toString());
|
||||
|
||||
boolean addDiscoveredComponent = true;
|
||||
for (Thing thing : things) {
|
||||
if (thing.getUID().equals(discoveredThingId)) {
|
||||
addDiscoveredComponent = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (addDiscoveredComponent) {
|
||||
String label = component.getName();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(4);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, component.getSerialNumber().toString());
|
||||
properties.put(PROPERTY_NAME, component.getName());
|
||||
properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME);
|
||||
properties.put(PROPERTY_MODEL, component.getSerialNumber().getComponentType());
|
||||
|
||||
String zoneName = getZoneName(component.getZoneId());
|
||||
if (zoneName != null) {
|
||||
properties.put(PROPERTY_ZONE, zoneName);
|
||||
}
|
||||
|
||||
int zoneId = component.getTemperatureSensorForZoneId();
|
||||
if (zoneId >= 0) {
|
||||
String tempForZoneName = getZoneName(zoneId);
|
||||
if (tempForZoneName != null) {
|
||||
properties.put(PROPERTY_TEMPERATURE_SENSOR_FOR_ZONE, tempForZoneName);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Adding device {} to inbox", discoveredThingId);
|
||||
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(discoveredThingId).withBridge(bridge)
|
||||
.withLabel(label).withProperties(properties)
|
||||
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String getZoneName(int zoneId) {
|
||||
Zone zone = bridgeHandler.getZone(zoneId);
|
||||
if (null == zone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return zone.getName();
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* A Component in the Nobø Hub can be a oven, a floor or a switch.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Component {
|
||||
|
||||
private final SerialNumber serialNumber;
|
||||
private final String name;
|
||||
private final boolean reverse;
|
||||
private final int zoneId;
|
||||
private final int temperatureSensorForZoneId;
|
||||
private double temperature;
|
||||
|
||||
public Component(SerialNumber serialNumber, String name, boolean reverse, int zoneId,
|
||||
int temperatureSensorForZoneId) {
|
||||
this.serialNumber = serialNumber;
|
||||
this.name = name;
|
||||
this.reverse = reverse;
|
||||
this.zoneId = zoneId;
|
||||
this.temperatureSensorForZoneId = temperatureSensorForZoneId;
|
||||
}
|
||||
|
||||
public static Component fromH02(String h02) throws NoboDataException {
|
||||
String[] parts = h02.split(" ", 8);
|
||||
|
||||
if (parts.length != 8) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on H2 call: %d", parts.length));
|
||||
}
|
||||
|
||||
SerialNumber serial = new SerialNumber(ModelHelper.toJavaString(parts[1]));
|
||||
if (!serial.isWellFormed()) {
|
||||
throw new NoboDataException(String.format("Illegal serial number: '%s'", serial));
|
||||
}
|
||||
|
||||
return new Component(serial, ModelHelper.toJavaString(parts[3]), "1".equals(parts[4]),
|
||||
Integer.parseInt(parts[5]), Integer.parseInt(parts[7]));
|
||||
}
|
||||
|
||||
public String generateCommandString(final String command) {
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
joiner.add(command).add(ModelHelper.toHubString(serialNumber.toString()));
|
||||
|
||||
// Status not yet implemented in hub
|
||||
joiner.add("0");
|
||||
|
||||
joiner.add(ModelHelper.toHubString(name)).add(reverse ? "1" : "0").add(Integer.toString(zoneId)).add("-1");
|
||||
|
||||
// Active Override ID not implemented in hub for components yet
|
||||
joiner.add(Integer.toString(temperatureSensorForZoneId));
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
public SerialNumber getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean inReverse() {
|
||||
return reverse;
|
||||
}
|
||||
|
||||
public int getZoneId() {
|
||||
return zoneId;
|
||||
}
|
||||
|
||||
public int getTemperatureSensorForZoneId() {
|
||||
return temperatureSensorForZoneId;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setTemperature(double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Stores a mapping between component ids and components that exists.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ComponentRegister {
|
||||
|
||||
private final @NotNull Map<SerialNumber, Component> register = new HashMap<SerialNumber, Component>();
|
||||
|
||||
/**
|
||||
* Stores a new Component in the register. If a component exists with the same id, that value is overwritten.
|
||||
*
|
||||
* @param component The Component to store.
|
||||
*/
|
||||
public void put(Component component) {
|
||||
register.put(component.getSerialNumber(), component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a component from the registry.
|
||||
*
|
||||
* @param componentId The component to remove
|
||||
* @return The component that is removed. Null if the component is not found.
|
||||
*/
|
||||
public @Nullable Component remove(SerialNumber componentId) {
|
||||
return register.remove(componentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a component from the registry.
|
||||
*
|
||||
* @param componentId The id of the component to return.
|
||||
* @return Returns the component, or null if it doesn't exist in the regestry.
|
||||
*/
|
||||
public @Nullable Component get(SerialNumber componentId) {
|
||||
return register.get(componentId);
|
||||
}
|
||||
|
||||
public Collection<Component> values() {
|
||||
return register.values();
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains information about the Hub we are communicating with.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Hub {
|
||||
|
||||
private final SerialNumber serialNumber;
|
||||
|
||||
private final String name;
|
||||
|
||||
private int activeOverrideId;
|
||||
|
||||
private final int defaultAwayOverrideLength;
|
||||
|
||||
private final String softwareVersion;
|
||||
|
||||
private final String hardwareVersion;
|
||||
|
||||
private final String productionDate;
|
||||
|
||||
public Hub(SerialNumber serialNumber, String name, int defaultAwayOverrideLength, int activeOverrideId,
|
||||
String softwareVersion, String hardwareVersion, String productionDate) {
|
||||
this.serialNumber = serialNumber;
|
||||
this.name = name;
|
||||
this.defaultAwayOverrideLength = defaultAwayOverrideLength;
|
||||
this.activeOverrideId = activeOverrideId;
|
||||
this.softwareVersion = softwareVersion;
|
||||
this.hardwareVersion = hardwareVersion;
|
||||
this.productionDate = productionDate;
|
||||
}
|
||||
|
||||
public static Hub fromH05(String h05) throws NoboDataException {
|
||||
String parts[] = h05.split(" ", 8);
|
||||
|
||||
if (parts.length != 8) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on H5 call: %d", parts.length));
|
||||
}
|
||||
|
||||
return new Hub(new SerialNumber(ModelHelper.toJavaString(parts[1])), ModelHelper.toJavaString(parts[2]),
|
||||
Integer.parseInt(parts[3]), Integer.parseInt(parts[4]), ModelHelper.toJavaString(parts[5]),
|
||||
ModelHelper.toJavaString(parts[6]), ModelHelper.toJavaString(parts[7]));
|
||||
}
|
||||
|
||||
public String generateCommandString(final String command) {
|
||||
return String.join(" ", command, serialNumber.toString(), ModelHelper.toHubString(name),
|
||||
Integer.toString(defaultAwayOverrideLength), Integer.toString(activeOverrideId),
|
||||
ModelHelper.toHubString(softwareVersion), ModelHelper.toHubString(hardwareVersion),
|
||||
ModelHelper.toHubString(productionDate));
|
||||
}
|
||||
|
||||
public SerialNumber getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Duration getDefaultAwayOverrideLength() {
|
||||
return Duration.ofMinutes(defaultAwayOverrideLength);
|
||||
}
|
||||
|
||||
public int getActiveOverrideId() {
|
||||
return activeOverrideId;
|
||||
}
|
||||
|
||||
public void setActiveOverrideId(int id) {
|
||||
activeOverrideId = id;
|
||||
}
|
||||
|
||||
public String getSoftwareVersion() {
|
||||
return softwareVersion;
|
||||
}
|
||||
|
||||
public String getHardwareVersion() {
|
||||
return hardwareVersion;
|
||||
}
|
||||
|
||||
public String getProductionDate() {
|
||||
return productionDate;
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBindingConstants;
|
||||
|
||||
/**
|
||||
* Helper class for converting data to/from Nobø Hub.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ModelHelper {
|
||||
|
||||
/**
|
||||
* Converts a String returned form Nobø hub to a normal Java string.
|
||||
*
|
||||
* @param noboString String where Char 160 (nobr space is used for space)
|
||||
* @return String with normal spaces.
|
||||
*/
|
||||
static String toJavaString(final String noboString) {
|
||||
return noboString.replace((char) 160, ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a String in java to a string the Nobø hub can understand (fix spaces).
|
||||
*
|
||||
* @param javaString String to send to Nobø hub
|
||||
* @return String with Nobø hub spaces
|
||||
*/
|
||||
static String toHubString(final String javaString) {
|
||||
return javaString.replace(' ', (char) 160);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Java date string from a date string returned from the Nobø Hub.
|
||||
*
|
||||
* @param noboDateString Date string from Nobø, like '202001221832' or '-1'
|
||||
* @return Java date for the returned string (or null if -1 is returned)
|
||||
*/
|
||||
@Nullable
|
||||
static LocalDateTime toJavaDate(final String noboDateString) throws NoboDataException {
|
||||
if ("-1".equals(noboDateString)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return LocalDateTime.parse(noboDateString, NoboHubBindingConstants.DATE_FORMAT_MINUTES);
|
||||
} catch (DateTimeParseException pe) {
|
||||
throw new NoboDataException(String.format("Failed parsing string %s", noboDateString), pe);
|
||||
}
|
||||
}
|
||||
|
||||
static String toHubDateMinutes(final @Nullable LocalDateTime date) {
|
||||
if (null == date) {
|
||||
return "-1";
|
||||
}
|
||||
|
||||
try {
|
||||
return date.format(NoboHubBindingConstants.DATE_FORMAT_MINUTES);
|
||||
} catch (DateTimeException dte) {
|
||||
return "-1";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception thrown when failing to communicate with the hub.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboCommunicationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -620277949858983367L;
|
||||
|
||||
public NoboCommunicationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoboCommunicationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception thrown when the data received from the hub has unexpected format.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NoboDataException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -620277949858983367L;
|
||||
|
||||
public NoboDataException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoboDataException(String message, Throwable parent) {
|
||||
super(message, parent);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The mode of the {@link OverridePlan}. What the value is overridden to.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum OverrideMode {
|
||||
|
||||
NORMAL(0),
|
||||
COMFORT(1),
|
||||
ECO(2),
|
||||
AWAY(3);
|
||||
|
||||
private final int numValue;
|
||||
|
||||
OverrideMode(int numValue) {
|
||||
this.numValue = numValue;
|
||||
}
|
||||
|
||||
public static OverrideMode getByNumber(int value) throws NoboDataException {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return NORMAL;
|
||||
case 1:
|
||||
return COMFORT;
|
||||
case 2:
|
||||
return ECO;
|
||||
case 3:
|
||||
return AWAY;
|
||||
default:
|
||||
throw new NoboDataException(String.format("Unknown override mode %d", value));
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumValue() {
|
||||
return numValue;
|
||||
}
|
||||
|
||||
public static OverrideMode getByName(String name) throws NoboDataException {
|
||||
if (name.isEmpty()) {
|
||||
throw new NoboDataException("Missing name");
|
||||
}
|
||||
|
||||
if ("Normal".equalsIgnoreCase(name)) {
|
||||
return NORMAL;
|
||||
} else if ("Comfort".equalsIgnoreCase(name)) {
|
||||
return COMFORT;
|
||||
} else if ("Eco".equalsIgnoreCase(name)) {
|
||||
return ECO;
|
||||
} else if ("Away".equalsIgnoreCase(name)) {
|
||||
return AWAY;
|
||||
}
|
||||
|
||||
throw new NoboDataException(String.format("Unknown name of override mode: '%s'", name));
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An override is when the normal weekly program is not followed because it is specified by pressing a switch or using
|
||||
* an app.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class OverridePlan {
|
||||
|
||||
private final int id;
|
||||
private final OverrideMode mode;
|
||||
private final OverrideType type;
|
||||
private final @Nullable LocalDateTime startTime;
|
||||
private final @Nullable LocalDateTime endTime;
|
||||
private final OverrideTarget target;
|
||||
private final int targetId;
|
||||
|
||||
public OverridePlan(int id, OverrideMode mode, OverrideType type, @Nullable LocalDateTime startTime,
|
||||
@Nullable LocalDateTime endTime, OverrideTarget target, int targetId) {
|
||||
this.id = id;
|
||||
this.mode = mode;
|
||||
this.type = type;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.target = target;
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
public static OverridePlan fromH04(String h04) throws NoboDataException {
|
||||
String[] parts = h04.split(" ", 8);
|
||||
|
||||
if (parts.length != 8) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on H4 call: %d", parts.length));
|
||||
}
|
||||
|
||||
return new OverridePlan(Integer.parseInt(parts[1]), OverrideMode.getByNumber(Integer.parseInt(parts[2])),
|
||||
OverrideType.getByNumber(Integer.parseInt(parts[3])), ModelHelper.toJavaDate(parts[4]),
|
||||
ModelHelper.toJavaDate(parts[5]), OverrideTarget.getByNumber(Integer.parseInt(parts[6])),
|
||||
Integer.parseInt(parts[7]));
|
||||
}
|
||||
|
||||
public static OverridePlan fromMode(OverrideMode mode, LocalDateTime date) {
|
||||
return new OverridePlan(1, mode, OverrideType.NOW, null, null, OverrideTarget.HUB, -1);
|
||||
}
|
||||
|
||||
public String generateCommandString(final String command) {
|
||||
return String.join(" ", command, Integer.toString(id), Integer.toString(mode.getNumValue()),
|
||||
Integer.toString(type.getNumValue()), ModelHelper.toHubDateMinutes(startTime),
|
||||
ModelHelper.toHubDateMinutes(endTime), Integer.toString(target.getNumValue()),
|
||||
Integer.toString(targetId));
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public OverrideMode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public OverrideType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public @Nullable LocalDateTime startTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public @Nullable LocalDateTime endTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public OverrideTarget getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public int getTargetId() {
|
||||
return targetId;
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Stores a mapping between override ids and overrides that are in place.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class OverrideRegister {
|
||||
|
||||
private final @NotNull Map<Integer, OverridePlan> register = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Stores a new Override in the register. If an override exists with the same id, that value is overwritten.
|
||||
*
|
||||
* @param overridePlan The Override to store.
|
||||
*/
|
||||
public void put(OverridePlan overridePlan) {
|
||||
register.put(overridePlan.getId(), overridePlan);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an override from the registry.
|
||||
*
|
||||
* @param overrideId The override to remove
|
||||
* @return The override that is removed. Null if the override is not found.
|
||||
*/
|
||||
public @Nullable OverridePlan remove(int overrideId) {
|
||||
return register.remove(overrideId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Override from the registry.
|
||||
*
|
||||
* @param overrideId The id of the override to return.
|
||||
* @return Returns the override, or null if it doesnt exist in the regestry.
|
||||
*/
|
||||
public @Nullable OverridePlan get(int overrideId) {
|
||||
return register.get(overrideId);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The target of the {@link OverridePlan}. What it applies to.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum OverrideTarget {
|
||||
|
||||
HUB(0),
|
||||
ZONE(1),
|
||||
COMPONENT(2);
|
||||
|
||||
private final int numValue;
|
||||
|
||||
private OverrideTarget(int numValue) {
|
||||
this.numValue = numValue;
|
||||
}
|
||||
|
||||
public static OverrideTarget getByNumber(int value) throws NoboDataException {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return HUB;
|
||||
case 1:
|
||||
return ZONE;
|
||||
case 2:
|
||||
return COMPONENT;
|
||||
default:
|
||||
throw new NoboDataException(String.format("Unknown override target %d", value));
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumValue() {
|
||||
return numValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The type of the {@link OverridePlan}. How long does it last.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum OverrideType {
|
||||
|
||||
NOW(0),
|
||||
TIMER(1),
|
||||
FROM_TO(2),
|
||||
CONSTANT(3);
|
||||
|
||||
private final int numValue;
|
||||
|
||||
OverrideType(int numValue) {
|
||||
this.numValue = numValue;
|
||||
}
|
||||
|
||||
public static OverrideType getByNumber(int value) throws NoboDataException {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return NOW;
|
||||
case 1:
|
||||
return TIMER;
|
||||
case 2:
|
||||
return FROM_TO;
|
||||
case 3:
|
||||
return CONSTANT;
|
||||
default:
|
||||
throw new NoboDataException(String.format("Unknown override type %d", value));
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumValue() {
|
||||
return numValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBindingConstants;
|
||||
|
||||
/**
|
||||
* Nobø serial numbers are 12 digits where 3 and 3 digits form 2 bytes as decimal. In total 32 bits.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class SerialNumber {
|
||||
|
||||
private final String serialNumber;
|
||||
|
||||
public SerialNumber(String serialNumber) {
|
||||
this.serialNumber = serialNumber.trim();
|
||||
}
|
||||
|
||||
public boolean isWellFormed() {
|
||||
if (serialNumber.length() != 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> parts = new ArrayList<>(4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
parts.add(serialNumber.substring((i * 3), (i * 3) + 3));
|
||||
}
|
||||
|
||||
if (parts.size() != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String part : parts) {
|
||||
try {
|
||||
int num = Integer.parseInt(part);
|
||||
if (num < 0 || num > 255) {
|
||||
return false;
|
||||
}
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type string.
|
||||
*/
|
||||
public String getTypeIdentifier() {
|
||||
if (!isWellFormed()) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
return serialNumber.substring(0, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of this component.
|
||||
*/
|
||||
public String getComponentType() {
|
||||
String id = getTypeIdentifier();
|
||||
String type = getTypeForSerialNumber(id);
|
||||
if (null != type) {
|
||||
return type;
|
||||
}
|
||||
|
||||
return "Unknown, please contact maintainer to add a new type for " + serialNumber;
|
||||
}
|
||||
|
||||
private @Nullable String getTypeForSerialNumber(String id) {
|
||||
return NoboHubBindingConstants.SERIALNUMBERS_FOR_TYPES.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj == null || obj.getClass() != this.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SerialNumber other = (SerialNumber) obj;
|
||||
return this.serialNumber.equals(other.serialNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.serialNumber.hashCode();
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Nobø serial numbers are 12 digits where 3 and 3 digits form 2 bytes as decimal. In total 32 bits.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Temperature {
|
||||
|
||||
private final SerialNumber serialNumber;
|
||||
private final double temperature;
|
||||
|
||||
public Temperature(SerialNumber serialNumber, double temperature) {
|
||||
this.serialNumber = serialNumber;
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public static Temperature fromY02(String y02) throws NoboDataException {
|
||||
String parts[] = y02.split(" ", 3);
|
||||
if (parts.length != 3) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on Y02 call: %d", parts.length));
|
||||
}
|
||||
|
||||
if (parts[2] == null) {
|
||||
throw new NoboDataException("Missing temperature data");
|
||||
}
|
||||
|
||||
SerialNumber serialNumber = new SerialNumber(parts[1]);
|
||||
double temp = Double.NaN;
|
||||
|
||||
if (!"N/A".equals(parts[2])) {
|
||||
try {
|
||||
temp = Double.parseDouble(parts[2]);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new NoboDataException(
|
||||
String.format("Failed to parse temperature %s: %s", parts[2], nfe.getMessage()), nfe);
|
||||
}
|
||||
}
|
||||
|
||||
return new Temperature(serialNumber, temp);
|
||||
}
|
||||
|
||||
public SerialNumber getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.nobohub.internal.NoboHubBindingConstants;
|
||||
|
||||
/**
|
||||
* The normal week profile (used when no {@link OverridePlan}s exist).
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class WeekProfile {
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final String profile;
|
||||
|
||||
public WeekProfile(int id, String name, String profile) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public static WeekProfile fromH03(String h03) throws NoboDataException {
|
||||
String[] parts = h03.split(" ", 4);
|
||||
|
||||
if (parts.length != 4) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on H3 call: %d", parts.length));
|
||||
}
|
||||
|
||||
return new WeekProfile(Integer.parseInt(parts[1]), ModelHelper.toJavaString(parts[2]),
|
||||
ModelHelper.toJavaString(parts[3]));
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current status on the week profile (unless there is an override).
|
||||
*
|
||||
* @param time The current time
|
||||
* @return The current status (according to the week profile)
|
||||
*/
|
||||
public WeekProfileStatus getStatusAt(LocalDateTime time) throws NoboDataException {
|
||||
final DayOfWeek weekDay = time.getDayOfWeek();
|
||||
final int dayNumber = weekDay.getValue();
|
||||
final String timeString = time.format(NoboHubBindingConstants.TIME_FORMAT_MINUTES);
|
||||
String[] parts = profile.split(",");
|
||||
|
||||
int dayCounter = 0;
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String current = parts[i];
|
||||
if (current.startsWith("0000")) {
|
||||
dayCounter++;
|
||||
}
|
||||
|
||||
if (current.length() != 5) {
|
||||
throw new NoboDataException("Illegal week profile entry: " + current);
|
||||
}
|
||||
|
||||
if (dayNumber == dayCounter) {
|
||||
String next = "24000";
|
||||
if (i + 1 < parts.length) {
|
||||
if (!parts[i + 1].startsWith("0000")) {
|
||||
next = parts[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (next.length() != 5) {
|
||||
throw new NoboDataException("Illegal week profile entry for next entry: " + next);
|
||||
}
|
||||
|
||||
try {
|
||||
String currentTime = current.substring(0, 4);
|
||||
String nextTime = next.substring(0, 4);
|
||||
if (currentTime.compareTo(timeString) <= 0 && timeString.compareTo(nextTime) < 0) {
|
||||
try {
|
||||
return WeekProfileStatus.getByNumber(Integer.parseInt(String.valueOf(current.charAt(4))));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new NoboDataException("Failed parsing week profile entry: " + current, nfe);
|
||||
}
|
||||
}
|
||||
} catch (IndexOutOfBoundsException oobe) {
|
||||
throw new NoboDataException("Illegal time string" + current + ", " + next, oobe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new NoboDataException(
|
||||
String.format("Failed to calculate %s for day %d in '%s'", timeString, dayNumber, profile));
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Stores a mapping between week profile ids and week profiles that exists.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class WeekProfileRegister {
|
||||
|
||||
private @NotNull Map<Integer, WeekProfile> register = new HashMap<Integer, WeekProfile>();
|
||||
|
||||
/**
|
||||
* Stores a new week profile in the register. If an week profile exists with the same id, that value is overwritten.
|
||||
*
|
||||
* @param profile The week profile to store.
|
||||
*/
|
||||
public void put(WeekProfile profile) {
|
||||
register.put(profile.getId(), profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a WeekProfile from the registry.
|
||||
*
|
||||
* @param weekProfileId The week profile to remove
|
||||
* @return The week profile that is removed. Null if the week profile is not found.
|
||||
*/
|
||||
public @Nullable WeekProfile remove(int weekProfileId) {
|
||||
return register.remove(weekProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a WeekProfile from the registry.
|
||||
*
|
||||
* @param weekProfileId The id of the week profile to return.
|
||||
* @return Returns the week profile, or null if it doesnt exist in the registry.
|
||||
*/
|
||||
public @Nullable WeekProfile get(int weekProfileId) {
|
||||
return register.get(weekProfileId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all WeekProfiles from the registry.
|
||||
*
|
||||
* @return Returns the week profile, or empty list if no profiles.
|
||||
*/
|
||||
public Collection<WeekProfile> values() {
|
||||
return register.values();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return register.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The status of the {@link WeekProfile}. What the value is in the week profile. Status OFF is matched both to value 3
|
||||
* and 4, while the documentation says 3, Hub with Hardware version 11123610_rev._1 and production date 20180305
|
||||
* will send value 4 for OFF.
|
||||
* compatibility.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum WeekProfileStatus {
|
||||
|
||||
ECO(0),
|
||||
COMFORT(1),
|
||||
AWAY(2),
|
||||
OFF(3);
|
||||
|
||||
private final int numValue;
|
||||
|
||||
private WeekProfileStatus(int numValue) {
|
||||
this.numValue = numValue;
|
||||
}
|
||||
|
||||
public static WeekProfileStatus getByNumber(int value) throws NoboDataException {
|
||||
switch (value) {
|
||||
case 0:
|
||||
return ECO;
|
||||
case 1:
|
||||
return COMFORT;
|
||||
case 2:
|
||||
return AWAY;
|
||||
case 3:
|
||||
case 4:
|
||||
return OFF;
|
||||
default:
|
||||
throw new NoboDataException(String.format("Unknown week profile status %d", value));
|
||||
}
|
||||
}
|
||||
|
||||
public int getNumValue() {
|
||||
return numValue;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A Zone contains one or more {@link Component}s.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class Zone {
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
private int activeWeekProfileId;
|
||||
private int comfortTemperature;
|
||||
private int ecoTemperature;
|
||||
private final boolean allowOverrides;
|
||||
private @Nullable Double temperature;
|
||||
|
||||
public Zone(int id, String name, int activeWeekProfileId, int comfortTemperature, int ecoTemperature,
|
||||
boolean allowOverrides) throws NoboDataException {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.activeWeekProfileId = activeWeekProfileId;
|
||||
this.comfortTemperature = comfortTemperature;
|
||||
this.ecoTemperature = ecoTemperature;
|
||||
this.allowOverrides = allowOverrides;
|
||||
}
|
||||
|
||||
public static Zone fromH01(String h01) throws NoboDataException {
|
||||
String parts[] = h01.split(" ", 8);
|
||||
|
||||
if (parts.length != 8) {
|
||||
throw new NoboDataException(
|
||||
String.format("Unexpected number of parts from hub on H1 call: %d", parts.length));
|
||||
}
|
||||
|
||||
return new Zone(Integer.parseInt(parts[1]), ModelHelper.toJavaString(parts[2]), Integer.parseInt(parts[3]),
|
||||
Integer.parseInt(parts[4]), Integer.parseInt(parts[5]), "1".equals(parts[6]));
|
||||
}
|
||||
|
||||
public String generateCommandString(final String command) {
|
||||
return String.join(" ", command, Integer.toString(id), ModelHelper.toHubString(name),
|
||||
Integer.toString(activeWeekProfileId), Integer.toString(comfortTemperature),
|
||||
Integer.toString(ecoTemperature), allowOverrides ? "1" : "0", "-1"); // "Active override id" is
|
||||
// deprecated
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getActiveWeekProfileId() {
|
||||
return activeWeekProfileId;
|
||||
}
|
||||
|
||||
public int getComfortTemperature() {
|
||||
return comfortTemperature;
|
||||
}
|
||||
|
||||
public int getEcoTemperature() {
|
||||
return ecoTemperature;
|
||||
}
|
||||
|
||||
public boolean getAllowOverrides() {
|
||||
return allowOverrides;
|
||||
}
|
||||
|
||||
public void setTemperature(@Nullable Double temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
public @Nullable Double getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
public void setComfortTemperature(int temp) {
|
||||
comfortTemperature = temp;
|
||||
}
|
||||
|
||||
public void setEcoTemperature(int temp) {
|
||||
ecoTemperature = temp;
|
||||
}
|
||||
|
||||
public void setWeekProfile(int weekProfileId) {
|
||||
activeWeekProfileId = weekProfileId;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Stores a mapping between zone ids and zones that exists.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
* @author Espen Fossen - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ZoneRegister {
|
||||
|
||||
private final @NotNull Map<Integer, Zone> register = new HashMap<Integer, Zone>();
|
||||
|
||||
/**
|
||||
* Stores a new Zone in the register. If a zone exists with the same id, that value is overwritten.
|
||||
*
|
||||
* @param zone The Zone to store.
|
||||
*/
|
||||
public void put(Zone zone) {
|
||||
register.put(zone.getId(), zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a zone from the registry.
|
||||
*
|
||||
* @param zoneId The zone to remove
|
||||
* @return The zone that is removed. Null if the zone is not found.
|
||||
*/
|
||||
public @Nullable Zone remove(int zoneId) {
|
||||
return register.remove(zoneId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Zone from the registry.
|
||||
*
|
||||
* @param zoneId The id of the zone to return.
|
||||
* @return Returns the zone, or null if it doesnt exist in the regestry.
|
||||
*/
|
||||
public @Nullable Zone get(int zoneId) {
|
||||
return register.get(zoneId);
|
||||
}
|
||||
|
||||
public Collection<Zone> values() {
|
||||
return register.values();
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="nobohub" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Glen Dimplex Nobø Hub Binding</name>
|
||||
<description>This is the binding for Glen Dimplex Nobø Hub.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,57 @@
|
||||
# binding
|
||||
|
||||
binding.nobohub.name = Glen Dimplex Nobø Hub Binding
|
||||
binding.nobohub.description = This is the binding for Glen Dimplex Nobø Hub.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.nobohub.component.label = Component
|
||||
thing-type.nobohub.component.description = A component is an oven, a switch or a floor thermostat
|
||||
thing-type.nobohub.nobohub.label = Nobø Hub
|
||||
thing-type.nobohub.nobohub.description = Nobø Hub Bridge Binding
|
||||
thing-type.nobohub.zone.label = Zone
|
||||
thing-type.nobohub.zone.description = A zone can contain several Nobø devices
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.nobohub.component.serialNumber.label = Serial Number
|
||||
thing-type.config.nobohub.component.serialNumber.description = Serial number of the component (12 digits)
|
||||
thing-type.config.nobohub.nobohub.hostName.label = Host Name
|
||||
thing-type.config.nobohub.nobohub.hostName.description = Host Name/IP address of the Nobø Hub
|
||||
thing-type.config.nobohub.nobohub.keepaliveInterval.label = Polling interval
|
||||
thing-type.config.nobohub.nobohub.keepaliveInterval.description = Polling interval (seconds). Default: 14.
|
||||
thing-type.config.nobohub.nobohub.serialNumber.label = Serial Number
|
||||
thing-type.config.nobohub.nobohub.serialNumber.description = Serial number of the Nobø hub (12 numbers, no spaces)
|
||||
thing-type.config.nobohub.zone.id.label = Id
|
||||
thing-type.config.nobohub.zone.id.description = Id of the Zone
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.nobohub.activeOverrideName-channel-type.label = Active Override
|
||||
channel-type.nobohub.activeOverrideName-channel-type.description = Mode of active override, using one of the predefined states supported
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.NORMAL = Normal
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.COMFORT = Comfort
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.ECO = Eco
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.Away = Away
|
||||
channel-type.nobohub.activeWeekProfile-channel-type.label = Active Week Profile Id
|
||||
channel-type.nobohub.activeWeekProfile-channel-type.description = Id of the active week profile, set via the Nobø app
|
||||
channel-type.nobohub.activeWeekProfileName-channel-type.label = Active Week Profile Name
|
||||
channel-type.nobohub.activeWeekProfileName-channel-type.description = Name of the active week profile, set via the Nobø app
|
||||
channel-type.nobohub.comfort-temperature-channel-type.label = Comfort Temperature
|
||||
channel-type.nobohub.comfort-temperature-channel-type.description = The preferred Comfort temperature level set on the heater or in the binding
|
||||
channel-type.nobohub.eco-temperature-channel-type.label = Eco Temperature
|
||||
channel-type.nobohub.eco-temperature-channel-type.description = The preferred Eco temperature level set on the heater or in the binding
|
||||
channel-type.nobohub.temperature-channel-type.label = Current Temperature
|
||||
channel-type.nobohub.temperature-channel-type.description = The current temperature from a device that supports reporting temperatures
|
||||
channel-type.nobohub.weekProfiles-channel-type.label = Week Profiles
|
||||
channel-type.nobohub.weekProfiles-channel-type.description = Name of the active week profile, set via the Nobø app
|
||||
|
||||
# User Messages
|
||||
message.missing.serial = Missing serial number in configuration
|
||||
message.bridge.status.failed = Failed to get status: {0}
|
||||
message.bridge.missing.hostname = Missing host name in configuration
|
||||
message.bridge.connection.failed = Failed to connect, check network connectivity and configuration
|
||||
message.component.illegal.serial = Illegal serial number: {0}
|
||||
message.component.notfound = Could not find Component with serial number {0} for channel {1}
|
||||
message.component.missing.id = Id not set for channel {0}
|
||||
message.zone.notfound = Could not find Zone with id {0} for channel {1}
|
@ -0,0 +1,57 @@
|
||||
# binding
|
||||
|
||||
binding.nobohub.name = Glen Dimplex Nobø Hub Binding
|
||||
binding.nobohub.description = Dette er en binding for Glen Dimplex Nobø Hub.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.nobohub.component.label = Komponent
|
||||
thing-type.nobohub.component.description = En komponent kan være en panelovn, bryter eller gulv termostat
|
||||
thing-type.nobohub.nobohub.label = Nobø Hub
|
||||
thing-type.nobohub.nobohub.description = Nobø Hub Bru Binding
|
||||
thing-type.nobohub.zone.label = Sone
|
||||
thing-type.nobohub.zone.description = En sone kan inneholde flere Nobø enheter
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.nobohub.nobohub.serialNumber.label = Serialnummer
|
||||
thing-type.config.nobohub.nobohub.serialNumber.description = Nobø Hub serialnummer (12 tall)
|
||||
thing-type.config.nobohub.nobohub.hostName.label = Tjeneradresse
|
||||
thing-type.config.nobohub.nobohub.hostName.description = Tjener eller IP addresse til Nobø Hub
|
||||
thing-type.config.nobohub.nobohub.keepaliveInterval.label = Tidsintervall
|
||||
thing-type.config.nobohub.nobohub.keepaliveInterval.description = Tidsintervall (sekunder). Standardinnstilling: 14.
|
||||
thing-type.config.nobohub.component.serialNumber.label = Serialnummer
|
||||
thing-type.config.nobohub.component.serialNumber.description = Serialnummer for komponent (12 tall, uten mellomrom)
|
||||
thing-type.config.nobohub.zone.id.label = Id
|
||||
thing-type.config.nobohub.zone.id.description = Id for sone
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.nobohub.activeOverrideName-channel-type.label = Aktiv Overstyring
|
||||
channel-type.nobohub.activeOverrideName-channel-type.description = Modus for aktiv overstyring, bruker en av de predefinerte typene som støttes
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.NORMAL = Normal
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.COMFORT = Komfort
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.ECO = Eco
|
||||
channel-type.nobohub.activeOverrideName-channel-type.state.option.Away = Borte
|
||||
channel-type.nobohub.activeWeekProfile-channel-type.label = Aktiv Ukeprofil Id
|
||||
channel-type.nobohub.activeWeekProfile-channel-type.description = Id på nåværende aktiv ukesprofil
|
||||
channel-type.nobohub.activeWeekProfileName-channel-type.label = Aktiv Ukeprofil Navn
|
||||
channel-type.nobohub.activeWeekProfileName-channel-type.description = Navn på nåværende aktiv ukesprofil
|
||||
channel-type.nobohub.comfort-temperature-channel-type.label = Komfort Temperatur
|
||||
channel-type.nobohub.comfort-temperature-channel-type.description = Ønsket Komfort temperaturnivå satt på panel eller i binding
|
||||
channel-type.nobohub.eco-temperature-channel-type.label = Eco Temperatur
|
||||
channel-type.nobohub.eco-temperature-channel-type.description = Ønsket Eco temperaturnivå satt på panel eller i binding
|
||||
channel-type.nobohub.temperature-channel-type.label = Nåværende Temperatur
|
||||
channel-type.nobohub.temperature-channel-type.description = Nåværende temperatur fra en enhet som støtter rapportering av temperaturer
|
||||
channel-type.nobohub.weekProfiles-channel-type.label = Ukeprofiler
|
||||
channel-type.nobohub.weekProfiles-channel-type.description = Tilgjengelige ukesprofiler, satt opp via Nobø app
|
||||
|
||||
# User Messages
|
||||
message.missing.serial = Mangler serialnummer i konfigurasjon
|
||||
message.bridge.status.failed = Kunne ikke hente status: {0}
|
||||
message.bridge.missing.hostname = Mangler tjenernavn i konfigurasjon
|
||||
message.bridge.connection.failed = Kunne ikke koble til, sjekk nettverksforbindelsen og konfigurasjon
|
||||
message.component.illegal.serial = Serialnummer er ukjent eller feil: {0}
|
||||
message.component.notfound = Kunne ikke finne Komponent med serialnummer {0} for kanal {1}
|
||||
message.component.missing.id = Id er ikke satt for kanal {0}
|
||||
message.zone.notfound = Kunne ikke finne Sone med id {0} for kanal {1}
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="nobohub"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<bridge-type id="nobohub">
|
||||
<label>Nobo Hub</label>
|
||||
<description>Nobo Hub Bridge Binding</description>
|
||||
|
||||
<channels>
|
||||
<channel id="activeOverrideName" typeId="activeOverrideName-channel-type"/>
|
||||
<channel id="weekProfiles" typeId="weekProfiles-channel-type"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Glen Dimplex Nobo</property>
|
||||
</properties>
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialNumber" type="text" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>Serial number of the Nobo hub (12 numbers, no spaces)</description>
|
||||
</parameter>
|
||||
<parameter name="hostName" type="text" required="true">
|
||||
<label>Host Name</label>
|
||||
<description>Host Name/IP address of the Nobo Hub</description>
|
||||
</parameter>
|
||||
<parameter name="keepaliveInterval" type="integer" required="false" min="5">
|
||||
<label>Polling interval</label>
|
||||
<description>Polling interval (seconds). Default: 14</description>
|
||||
<default>14</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="nobohub"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="zone">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="nobohub"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Zone</label>
|
||||
<description>A zone can contain several Nobo devices</description>
|
||||
|
||||
<channels>
|
||||
<channel id="activeWeekProfileName" typeId="activeWeekProfileName-channel-type"/>
|
||||
<channel id="activeWeekProfile" typeId="activeWeekProfile-channel-type"/>
|
||||
<channel id="comfortTemperature" typeId="comfort-temperature-channel-type"/>
|
||||
<channel id="ecoTemperature" typeId="eco-temperature-channel-type"/>
|
||||
<channel id="currentTemperature" typeId="temperature-channel-type"/>
|
||||
<channel id="calculatedWeekProfileStatus" typeId="activeOverrideName-channel-type"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Glen Dimplex Nobo</property>
|
||||
</properties>
|
||||
<representation-property>name</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="id" type="integer" required="true">
|
||||
<label>Id</label>
|
||||
<description>Id of the Zone</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="component">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="nobohub"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Component</label>
|
||||
<description>A component is an oven, a switch or a floor thermostat</description>
|
||||
|
||||
<channels>
|
||||
<channel id="currentTemperature" typeId="temperature-channel-type"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Glen Dimplex Nobo</property>
|
||||
</properties>
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialNumber" type="text" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>Serial number of the component (12 digits)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="activeOverrideName-channel-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Active Override</label>
|
||||
<description>Name of active override, using one of the predefined states supported</description>
|
||||
<category>Heating</category>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="NORMAL">Normal</option>
|
||||
<option value="COMFORT">Comfort</option>
|
||||
<option value="ECO">Eco</option>
|
||||
<option value="Away">Away</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="eco-temperature-channel-type">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Eco Temperature</label>
|
||||
<description>The preferred Eco temperature level set on the heater or in the binding</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state min="7" max="30" step="1" pattern="%d °C" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="comfort-temperature-channel-type">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Comfort Temperature</label>
|
||||
<description>The preferred Comfort temperature level set on the heater or in the binding</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Setpoint</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state min="7" max="30" step="1" pattern="%d °C" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="temperature-channel-type">
|
||||
<item-type>Number:Temperature</item-type>
|
||||
<label>Current Temperature</label>
|
||||
<description>The current temperature from a device that supports reporting temperatures</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Temperature</tag>
|
||||
</tags>
|
||||
<state pattern="%.3f °C" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="activeWeekProfileName-channel-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Active Week Profile Name</label>
|
||||
<description>Name of the active week profile, set via the Nobo app</description>
|
||||
<category>Heating</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="activeWeekProfile-channel-type">
|
||||
<item-type>Number</item-type>
|
||||
<label>Active Week Profile Id</label>
|
||||
<description>Id of the active week profile, set via the Nobo app</description>
|
||||
<category>Heating</category>
|
||||
<state min="0" readOnly="false"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="weekProfiles-channel-type">
|
||||
<item-type>String</item-type>
|
||||
<label>Week Profiles</label>
|
||||
<description>List of active week profiles, set via the Nobo app</description>
|
||||
<category>Heating</category>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for ComponentRegister model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ComponentRegisterTest {
|
||||
|
||||
@Test
|
||||
public void testPutGet() throws NoboDataException {
|
||||
Component c = Component.fromH02("H02 186170024143 0 Kontor 0 1 -1 -1");
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
sut.put(c);
|
||||
Assertions.assertEquals(c, sut.get(c.getSerialNumber()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutOverwrite() throws NoboDataException {
|
||||
Component c1 = Component.fromH02("H02 186170024143 0 Kontor 0 1 -1 -1");
|
||||
Component c2 = Component.fromH02("H02 186170024143 0 Bad 0 1 -1 -1");
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
sut.put(c1);
|
||||
sut.put(c2);
|
||||
Assertions.assertEquals(c2, sut.get(c2.getSerialNumber()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws NoboDataException {
|
||||
Component c = Component.fromH02("H02 186170024143 0 Kontor 0 1 -1 -1");
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
sut.put(c);
|
||||
Component res = sut.remove(c.getSerialNumber());
|
||||
Assertions.assertEquals(c, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveUnknown() {
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
Component res = sut.remove(new SerialNumber("123123123123"));
|
||||
Assertions.assertEquals(null, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknown() {
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
Component z = sut.get(new SerialNumber("123123123123"));
|
||||
Assertions.assertEquals(null, z);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValues() throws NoboDataException {
|
||||
Component c1 = Component.fromH02("H02 186170024141 0 Kontor 0 1 -1 -1");
|
||||
Component c2 = Component.fromH02("H02 186170024142 0 Soverom 0 1 -1 -1");
|
||||
ComponentRegister sut = new ComponentRegister();
|
||||
sut.put(c1);
|
||||
sut.put(c2);
|
||||
Assertions.assertEquals(2, sut.values().size());
|
||||
Assertions.assertEquals(true, sut.values().contains(c1));
|
||||
Assertions.assertEquals(true, sut.values().contains(c2));
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Component model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ComponentTest {
|
||||
@Test
|
||||
public void testParseH02() throws NoboDataException {
|
||||
Component comp = Component.fromH02("H02 186170024143 0 Kontor 0 1 -1 -1");
|
||||
comp.setTemperature(12.3);
|
||||
assertEquals(new SerialNumber("186170024143"), comp.getSerialNumber());
|
||||
assertEquals("Kontor", comp.getName());
|
||||
assertEquals(1, comp.getZoneId());
|
||||
assertEquals(-1, comp.getTemperatureSensorForZoneId());
|
||||
assertFalse(comp.inReverse());
|
||||
assertEquals(12.3, comp.getTemperature(), 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateU03() throws NoboDataException {
|
||||
Component comp = Component.fromH02("H02 186170024143 0 Kontor 0 1 -1 -1");
|
||||
assertEquals("U02 186170024143 0 Kontor 0 1 -1 -1", comp.generateCommandString("U02"));
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Hub model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class HubTest {
|
||||
|
||||
@Test
|
||||
public void testParseH05() throws NoboDataException {
|
||||
Hub hub = Hub.fromH05("H05 102000092118 My Eco Hub 2880 4 114 11123610_rev._1 20190426");
|
||||
assertEquals(new SerialNumber("102000092118"), hub.getSerialNumber());
|
||||
assertEquals("My Eco Hub", hub.getName());
|
||||
assertEquals(Duration.ofDays(2), hub.getDefaultAwayOverrideLength());
|
||||
assertEquals(4, hub.getActiveOverrideId());
|
||||
assertEquals("114", hub.getSoftwareVersion());
|
||||
assertEquals("11123610_rev._1", hub.getHardwareVersion());
|
||||
assertEquals("20190426", hub.getProductionDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseV03() throws NoboDataException {
|
||||
Hub hub = Hub.fromH05("V03 102000092118 My Eco Hub 2880 14 114 11123610_rev._1 20190426");
|
||||
assertEquals(new SerialNumber("102000092118"), hub.getSerialNumber());
|
||||
assertEquals("My Eco Hub", hub.getName());
|
||||
assertEquals(Duration.ofDays(2), hub.getDefaultAwayOverrideLength());
|
||||
assertEquals(14, hub.getActiveOverrideId());
|
||||
assertEquals("114", hub.getSoftwareVersion());
|
||||
assertEquals("11123610_rev._1", hub.getHardwareVersion());
|
||||
assertEquals("20190426", hub.getProductionDate());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateU03() throws NoboDataException {
|
||||
Hub hub = Hub.fromH05("V03 102000092118 My Eco Hub 2880 14 114 11123610_rev._1 20190426");
|
||||
assertEquals("U03 102000092118 My Eco Hub 2880 14 114 11123610_rev._1 20190426",
|
||||
hub.generateCommandString("U03"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanChangeOverride() throws NoboDataException {
|
||||
Hub hub = Hub.fromH05("V03 102000092118 My Eco Hub 2880 14 114 11123610_rev._1 20190426");
|
||||
hub.setActiveOverrideId(123);
|
||||
assertEquals("U03 102000092118 My Eco Hub 2880 123 114 11123610_rev._1 20190426",
|
||||
hub.generateCommandString("U03"));
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit test for ModelHelper class.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModelHelperTest {
|
||||
|
||||
@Test
|
||||
public void testParseJavaStringNoSpace() {
|
||||
assertEquals("NoSpace", ModelHelper.toJavaString("NoSpace"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseJavaStringNormalSpace() {
|
||||
assertEquals("Contains Space", ModelHelper.toJavaString("Contains Space"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseJavaStringNoBreakSpace() {
|
||||
assertEquals("Contains NoBreak Space", ModelHelper.toJavaString("Contains" + (char) 160 + "NoBreak Space"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateNoboStringNoSpace() {
|
||||
assertEquals("NoSpace", ModelHelper.toHubString("NoSpace"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateNoboStringNormalSpace() {
|
||||
assertEquals("Contains" + (char) 160 + "NoBreak", ModelHelper.toHubString("Contains" + (char) 160 + "NoBreak"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateNoboStringNoBreakSpace() {
|
||||
assertEquals("Contains" + (char) 160 + "NoBreak" + (char) 160 + "Space",
|
||||
ModelHelper.toHubString("Contains NoBreak Space"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNull() throws NoboDataException {
|
||||
assertNull(ModelHelper.toJavaDate("-1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseDate() throws NoboDataException {
|
||||
LocalDateTime date = LocalDateTime.of(2020, Month.JANUARY, 22, 19, 30);
|
||||
assertEquals(date, ModelHelper.toJavaDate("202001221930"));
|
||||
}
|
||||
|
||||
@Test()
|
||||
public void testParseIllegalDate() {
|
||||
assertThrows(NoboDataException.class, () -> ModelHelper.toJavaDate("20201322h1930"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateNoboDate() {
|
||||
LocalDateTime date = LocalDateTime.of(2020, Month.JANUARY, 22, 19, 30);
|
||||
assertEquals("202001221930", ModelHelper.toHubDateMinutes(date));
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for OverrideRegister model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OverridePlanRegisterTest {
|
||||
|
||||
@Test
|
||||
public void testPutGet() throws NoboDataException {
|
||||
OverridePlan o = OverridePlan.fromH04("H04 4 0 0 -1 -1 0 -1");
|
||||
OverrideRegister sut = new OverrideRegister();
|
||||
sut.put(o);
|
||||
assertEquals(o, sut.get(o.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutOverwrite() throws NoboDataException {
|
||||
OverridePlan o1 = OverridePlan.fromH04("H04 4 0 0 -1 -1 0 -1");
|
||||
OverridePlan o2 = OverridePlan.fromH04("H04 4 3 0 -1 -1 0 -1");
|
||||
OverrideRegister sut = new OverrideRegister();
|
||||
sut.put(o1);
|
||||
sut.put(o2);
|
||||
assertEquals(o2, sut.get(o2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws NoboDataException {
|
||||
OverridePlan o = OverridePlan.fromH04("H04 4 0 0 -1 -1 0 -1");
|
||||
OverrideRegister sut = new OverrideRegister();
|
||||
sut.put(o);
|
||||
OverridePlan res = sut.remove(o.getId());
|
||||
assertEquals(o, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveUnknown() {
|
||||
OverrideRegister sut = new OverrideRegister();
|
||||
OverridePlan res = sut.remove(666);
|
||||
assertNull(res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknown() {
|
||||
OverrideRegister sut = new OverrideRegister();
|
||||
OverridePlan o = sut.get(666);
|
||||
assertNull(o);
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Override model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OverridePlanTest {
|
||||
|
||||
@Test
|
||||
public void testParseH04DefaultOverride() throws NoboDataException {
|
||||
OverridePlan parsed = OverridePlan.fromH04("H04 4 0 0 -1 -1 0 -1");
|
||||
assertEquals(4, parsed.getId());
|
||||
assertEquals(OverrideMode.NORMAL, parsed.getMode());
|
||||
assertEquals(OverrideType.NOW, parsed.getType());
|
||||
assertEquals(OverrideTarget.HUB, parsed.getTarget());
|
||||
assertEquals(-1, parsed.getTargetId());
|
||||
assertNull(parsed.startTime());
|
||||
assertNull(parsed.endTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseB03WithStartDate() throws NoboDataException {
|
||||
OverridePlan parsed = OverridePlan.fromH04("B03 9 3 1 202001221930 -1 0 -1");
|
||||
assertEquals(9, parsed.getId());
|
||||
assertEquals(OverrideMode.AWAY, parsed.getMode());
|
||||
assertEquals(OverrideType.TIMER, parsed.getType());
|
||||
assertEquals(OverrideTarget.HUB, parsed.getTarget());
|
||||
assertEquals(-1, parsed.getTargetId());
|
||||
LocalDateTime date = LocalDateTime.of(2020, Month.JANUARY, 22, 19, 30);
|
||||
assertEquals(date, parsed.startTime());
|
||||
assertNull(parsed.endTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseS03NoDate() throws NoboDataException {
|
||||
OverridePlan parsed = OverridePlan.fromH04("S03 13 0 0 -1 -1 0 -1");
|
||||
assertEquals(13, parsed.getId());
|
||||
assertEquals(OverrideMode.NORMAL, parsed.getMode());
|
||||
assertEquals(OverrideType.NOW, parsed.getType());
|
||||
assertEquals(OverrideTarget.HUB, parsed.getTarget());
|
||||
assertEquals(-1, parsed.getTargetId());
|
||||
assertNull(parsed.startTime());
|
||||
assertNull(parsed.endTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddA03WithStartDate() throws NoboDataException {
|
||||
OverridePlan parsed = OverridePlan.fromH04("B03 9 3 1 202001221930 -1 0 -1");
|
||||
assertEquals("A03 9 3 1 202001221930 -1 0 -1", parsed.generateCommandString("A03"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFromMode() {
|
||||
LocalDateTime date = LocalDateTime.of(2020, Month.FEBRUARY, 21, 21, 42);
|
||||
OverridePlan overridePlan = OverridePlan.fromMode(OverrideMode.AWAY, date);
|
||||
assertEquals("A03 1 3 0 -1 -1 0 -1", overridePlan.generateCommandString("A03"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModeNames() throws NoboDataException {
|
||||
assertEquals(OverrideMode.AWAY, OverrideMode.getByName("Away"));
|
||||
assertEquals(OverrideMode.ECO, OverrideMode.getByName("ECO"));
|
||||
assertEquals(OverrideMode.NORMAL, OverrideMode.getByName("Normal"));
|
||||
assertEquals(OverrideMode.COMFORT, OverrideMode.getByName("COMFORT"));
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for serial number model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SerialNumberTest {
|
||||
|
||||
@Test
|
||||
public void testIsWellFormed() {
|
||||
assertTrue(new SerialNumber("123123123123").isWellFormed());
|
||||
assertFalse(new SerialNumber("123123123").isWellFormed());
|
||||
assertFalse(new SerialNumber("123 123 123 123").isWellFormed());
|
||||
assertFalse(new SerialNumber("123123123xyz").isWellFormed());
|
||||
assertFalse(new SerialNumber("123123123987").isWellFormed());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTypeIdentifier() {
|
||||
assertEquals("123", new SerialNumber("123123123123").getTypeIdentifier());
|
||||
assertEquals("Unknown", new SerialNumber("xyz").getTypeIdentifier());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetComponentType() {
|
||||
assertEquals("NTD-4R", new SerialNumber("186170024143").getComponentType());
|
||||
assertEquals("Nobø Switch", new SerialNumber("234001021010").getComponentType());
|
||||
assertEquals("Unknown, please contact maintainer to add a new type for 123123123123",
|
||||
new SerialNumber("123123123123").getComponentType());
|
||||
assertEquals("Unknown, please contact maintainer to add a new type for foobar",
|
||||
new SerialNumber("foobar").getComponentType());
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for temperature model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TemperatureTest {
|
||||
|
||||
@Test
|
||||
public void testParseY02() throws NoboDataException {
|
||||
Temperature temp = Temperature.fromY02("Y02 123123123123 12.345");
|
||||
assertEquals(new SerialNumber("123123123123"), temp.getSerialNumber());
|
||||
assertEquals(12.34, temp.getTemperature(), 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseY02NATemp() throws NoboDataException {
|
||||
Temperature temp = Temperature.fromY02("Y02 123123123123 N/A");
|
||||
assertEquals(new SerialNumber("123123123123"), temp.getSerialNumber());
|
||||
assertEquals(Double.NaN, temp.getTemperature(), 0.1);
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for WeekProfileRegister model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeekProfileRegisterTest {
|
||||
|
||||
@Test
|
||||
public void testPutGet() throws NoboDataException {
|
||||
WeekProfile p1 = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
sut.put(p1);
|
||||
assertEquals(p1, sut.get(p1.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutOverwrite() throws NoboDataException {
|
||||
WeekProfile p1 = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfile p2 = WeekProfile.fromH03(
|
||||
"H03 2 HomeOffice 00000,06001,09000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
sut.put(p1);
|
||||
sut.put(p2);
|
||||
assertEquals(p2, sut.get(p2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws NoboDataException {
|
||||
WeekProfile p1 = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
sut.put(p1);
|
||||
WeekProfile res = sut.remove(p1.getId());
|
||||
assertEquals(p1, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveUnknown() {
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
WeekProfile res = sut.remove(666);
|
||||
assertEquals(null, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknown() {
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
WeekProfile o = sut.get(666);
|
||||
assertEquals(null, o);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsEmpty() throws NoboDataException {
|
||||
WeekProfile p1 = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileRegister sut = new WeekProfileRegister();
|
||||
assertEquals(true, sut.isEmpty());
|
||||
sut.put(p1);
|
||||
assertEquals(false, sut.isEmpty());
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Month;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for WeekProfile model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class WeekProfileTest {
|
||||
|
||||
private static final LocalDateTime MONDAY = LocalDateTime.of(2020, Month.MAY, 11, 0, 0);
|
||||
private static final LocalDateTime WEDNESDAY = LocalDateTime.of(2020, Month.MAY, 13, 0, 0);
|
||||
private static final LocalDateTime SUNDAY = LocalDateTime.of(2020, Month.MAY, 17, 23, 59);
|
||||
|
||||
@Test
|
||||
public void testParseH03() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
Assertions.assertEquals(1, weekProfile.getId());
|
||||
Assertions.assertEquals("Default", weekProfile.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindFirstStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileStatus status = weekProfile.getStatusAt(MONDAY);
|
||||
Assertions.assertEquals(WeekProfileStatus.ECO, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindLastStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileStatus status = weekProfile.getStatusAt(SUNDAY);
|
||||
Assertions.assertEquals(WeekProfileStatus.ECO, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindEmptyDayStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03("H03 1 Default 00000,00000,00001,00000,00000,00000,00000");
|
||||
WeekProfileStatus status = weekProfile.getStatusAt(WEDNESDAY);
|
||||
Assertions.assertEquals(WeekProfileStatus.COMFORT, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindOffDayStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03("H03 2 Off 00004,00003,00004,00004,00004,00004,00003");
|
||||
WeekProfileStatus statusWen = weekProfile.getStatusAt(WEDNESDAY);
|
||||
Assertions.assertEquals(WeekProfileStatus.OFF, statusWen);
|
||||
WeekProfileStatus statusSat = weekProfile.getStatusAt(SUNDAY);
|
||||
Assertions.assertEquals(WeekProfileStatus.OFF, statusSat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindStartingNowStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileStatus status = weekProfile.getStatusAt(MONDAY.plusHours(6));
|
||||
Assertions.assertEquals(WeekProfileStatus.COMFORT, status);
|
||||
|
||||
status = weekProfile.getStatusAt(MONDAY.plusHours(6).plusMinutes(1));
|
||||
Assertions.assertEquals(WeekProfileStatus.COMFORT, status);
|
||||
|
||||
status = weekProfile.getStatusAt(MONDAY.plusHours(6).minusMinutes(1));
|
||||
Assertions.assertEquals(WeekProfileStatus.ECO, status);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindNormalStatus() throws NoboDataException {
|
||||
WeekProfile weekProfile = WeekProfile.fromH03(
|
||||
"H03 1 Default 00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,23000,00000,06001,08000,15001,00000,07001,00000,07001,23000");
|
||||
WeekProfileStatus status = weekProfile.getStatusAt(WEDNESDAY.plusHours(7).plusMinutes(13));
|
||||
Assertions.assertEquals(WeekProfileStatus.COMFORT, status);
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for ZoneRegister model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneRegisterTest {
|
||||
|
||||
@Test
|
||||
public void testPutGet() throws NoboDataException {
|
||||
Zone z = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
sut.put(z);
|
||||
assertEquals(z, sut.get(z.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutOverwrite() throws NoboDataException {
|
||||
Zone z1 = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
Zone z2 = Zone.fromH01("H01 1 2. etage 20 22 16 1 -1");
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
sut.put(z1);
|
||||
sut.put(z2);
|
||||
assertEquals(z2, sut.get(z2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() throws NoboDataException {
|
||||
Zone z = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
sut.put(z);
|
||||
Zone res = sut.remove(z.getId());
|
||||
assertEquals(z, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveUnknown() {
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
Zone res = sut.remove(666);
|
||||
assertEquals(null, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnknown() {
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
Zone z = sut.get(666);
|
||||
assertEquals(null, z);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValues() throws NoboDataException {
|
||||
Zone z1 = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
Zone z2 = Zone.fromH01("H01 2 2. etage 20 22 16 1 -1");
|
||||
ZoneRegister sut = new ZoneRegister();
|
||||
sut.put(z1);
|
||||
sut.put(z2);
|
||||
assertEquals(2, sut.values().size());
|
||||
assertEquals(true, sut.values().contains(z1));
|
||||
assertEquals(true, sut.values().contains(z2));
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
|
||||
package org.openhab.binding.nobohub.internal.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Zone model object.
|
||||
*
|
||||
* @author Jørgen Austvik - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ZoneTest {
|
||||
|
||||
@Test
|
||||
public void testParseH01Simple() throws NoboDataException {
|
||||
Zone zone = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
assertEquals(1, zone.getId());
|
||||
assertEquals("1. etage", zone.getName());
|
||||
assertEquals(20, zone.getActiveWeekProfileId());
|
||||
assertTrue(zone.getAllowOverrides());
|
||||
assertEquals(16, zone.getEcoTemperature());
|
||||
assertEquals(22, zone.getComfortTemperature());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateCommand() throws NoboDataException {
|
||||
Zone zone = Zone.fromH01("H01 1 1. etage 20 22 16 1 -1");
|
||||
assertEquals("U00 1 1. etage 20 22 16 1 -1", zone.generateCommandString("U00"));
|
||||
}
|
||||
}
|
@ -256,6 +256,7 @@
|
||||
<module>org.openhab.binding.nibeuplink</module>
|
||||
<module>org.openhab.binding.nikobus</module>
|
||||
<module>org.openhab.binding.nikohomecontrol</module>
|
||||
<module>org.openhab.binding.nobohub</module>
|
||||
<module>org.openhab.binding.novafinedust</module>
|
||||
<module>org.openhab.binding.ntp</module>
|
||||
<module>org.openhab.binding.nuki</module>
|
||||
|
Loading…
Reference in New Issue
Block a user