From 596b261d4738557392e1c1dfdc92dcd69bbaa46b Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Tue, 16 Feb 2021 20:22:27 +0000 Subject: [PATCH 001/118] [hdpowerview] Eliminate nightly crash dump in OH3 (#10118) * [hdpowerview] refactor from jax-rs to http client * [hdpowerview] adopt proposals of code reviewer * [hdpowerview] adopt additional proposals of code reviewer * [hdpowerview] provide exception class name Signed-off-by: Andrew Fiddian-Green --- .../internal/HDPowerViewHandlerFactory.java | 13 +- .../internal/HDPowerViewWebTargets.java | 202 ++++++++++-------- .../internal/HubProcessingException.java | 30 +++ .../HDPowerViewShadeDiscoveryService.java | 7 +- .../handler/HDPowerViewHubHandler.java | 19 +- .../handler/HDPowerViewShadeHandler.java | 23 +- .../hdpowerview/HDPowerViewJUnitTests.java | 51 +++-- 7 files changed, 212 insertions(+), 133 deletions(-) create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HubProcessingException.java diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java index 6fb6c4747296b..8b4dc8aeb8be1 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewHandlerFactory.java @@ -14,14 +14,14 @@ import java.util.Hashtable; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.hdpowerview.internal.discovery.HDPowerViewShadeDiscoveryService; import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewHubHandler; import org.openhab.binding.hdpowerview.internal.handler.HDPowerViewShadeHandler; import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingTypeUID; @@ -41,11 +41,12 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.hdpowerview") public class HDPowerViewHandlerFactory extends BaseThingHandlerFactory { - private final ClientBuilder clientBuilder; + + private final HttpClient httpClient; @Activate - public HDPowerViewHandlerFactory(@Reference ClientBuilder clientBuilder) { - this.clientBuilder = clientBuilder; + public HDPowerViewHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); } @Override @@ -58,7 +59,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_HUB)) { - HDPowerViewHubHandler handler = new HDPowerViewHubHandler((Bridge) thing, clientBuilder); + HDPowerViewHubHandler handler = new HDPowerViewHubHandler((Bridge) thing, httpClient); registerService(new HDPowerViewShadeDiscoveryService(handler)); return handler; } else if (thingTypeUID.equals(HDPowerViewBindingConstants.THING_TYPE_SHADE)) { diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java index 94174ba16348a..4d40d9927bee2 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HDPowerViewWebTargets.java @@ -13,17 +13,18 @@ package org.openhab.binding.hdpowerview.internal; import java.time.Instant; - -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.hdpowerview.internal.api.ShadePosition; import org.openhab.binding.hdpowerview.internal.api.requests.ShadeMove; import org.openhab.binding.hdpowerview.internal.api.requests.ShadeStop; @@ -45,14 +46,6 @@ @NonNullByDefault public class HDPowerViewWebTargets { - private static final String PUT = "PUT"; - private static final String GET = "GET"; - private static final String SCENE_ID = "sceneId"; - private static final String ID = "id"; - private static final String REFRESH = "refresh"; - private static final String CONN_HDR = "Connection"; - private static final String CONN_VAL = "close"; // versus "keep-alive" - private final Logger logger = LoggerFactory.getLogger(HDPowerViewWebTargets.class); /* @@ -64,128 +57,159 @@ public class HDPowerViewWebTargets { private final int maintenancePeriod = 300; private Instant maintenanceScheduledEnd = Instant.now().minusSeconds(2 * maintenancePeriod); - private WebTarget base; - private WebTarget shades; - private WebTarget shade; - private WebTarget sceneActivate; - private WebTarget scenes; + private final String base; + private final String shades; + private final String sceneActivate; + private final String scenes; private final Gson gson = new Gson(); + private final HttpClient httpClient; + + /** + * private helper class for passing http url query parameters + */ + private static class Query { + private final String key; + private final String value; + + private Query(String key, String value) { + this.key = key; + this.value = value; + } + + public static Query of(String key, String value) { + return new Query(key, value); + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + } /** * Initialize the web targets - * - * @param client the Javax RS client (the binding) + * + * @param httpClient the HTTP client (the binding) * @param ipAddress the IP address of the server (the hub) */ - public HDPowerViewWebTargets(Client client, String ipAddress) { - base = client.target("http://" + ipAddress + "/api"); - shades = base.path("shades/"); - shade = base.path("shades/{id}"); - sceneActivate = base.path("scenes"); - scenes = base.path("scenes/"); + public HDPowerViewWebTargets(HttpClient httpClient, String ipAddress) { + base = "http://" + ipAddress + "/api/"; + shades = base + "shades/"; + sceneActivate = base + "scenes"; + scenes = base + "scenes/"; + this.httpClient = httpClient; } /** * Fetches a JSON package that describes all shades in the hub, and wraps it in * a Shades class instance - * + * * @return Shades class instance * @throws JsonParseException if there is a JSON parsing error - * @throws ProcessingException if there is any processing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public @Nullable Shades getShades() throws JsonParseException, ProcessingException, HubMaintenanceException { - String json = invoke(shades.request().header(CONN_HDR, CONN_VAL).buildGet(), shades, null); + public @Nullable Shades getShades() throws JsonParseException, HubProcessingException, HubMaintenanceException { + String json = invoke(HttpMethod.GET, shades, null, null); return gson.fromJson(json, Shades.class); } /** * Instructs the hub to move a specific shade - * + * * @param shadeId id of the shade to be moved * @param position instance of ShadePosition containing the new position - * @throws ProcessingException if there is any processing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public void moveShade(int shadeId, ShadePosition position) throws ProcessingException, HubMaintenanceException { - WebTarget target = shade.resolveTemplate(ID, shadeId); + public void moveShade(int shadeId, ShadePosition position) throws HubProcessingException, HubMaintenanceException { String json = gson.toJson(new ShadeMove(shadeId, position)); - invoke(target.request().header(CONN_HDR, CONN_VAL) - .buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json); - return; + invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, json); } /** * Fetches a JSON package that describes all scenes in the hub, and wraps it in * a Scenes class instance - * + * * @return Scenes class instance * @throws JsonParseException if there is a JSON parsing error - * @throws ProcessingException if there is any processing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public @Nullable Scenes getScenes() throws JsonParseException, ProcessingException, HubMaintenanceException { - String json = invoke(scenes.request().header(CONN_HDR, CONN_VAL).buildGet(), scenes, null); + public @Nullable Scenes getScenes() throws JsonParseException, HubProcessingException, HubMaintenanceException { + String json = invoke(HttpMethod.GET, scenes, null, null); return gson.fromJson(json, Scenes.class); } /** * Instructs the hub to execute a specific scene - * + * * @param sceneId id of the scene to be executed - * @throws ProcessingException if there is any processing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public void activateScene(int sceneId) throws ProcessingException, HubMaintenanceException { - WebTarget target = sceneActivate.queryParam(SCENE_ID, sceneId); - invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null); + public void activateScene(int sceneId) throws HubProcessingException, HubMaintenanceException { + invoke(HttpMethod.GET, sceneActivate, Query.of("sceneId", Integer.toString(sceneId)), null); } - private synchronized String invoke(Invocation invocation, WebTarget target, @Nullable String jsonCommand) - throws ProcessingException, HubMaintenanceException { + /** + * Invoke a call on the hub server to retrieve information or send a command + * + * @param method GET or PUT + * @param url the host url to be called + * @param query the http query parameter + * @param jsonCommand the request command content (as a json string) + * @return the response content (as a json string) + * @throws HubProcessingException + * @throws HubMaintenanceException + * @throws HubProcessingException + */ + private synchronized String invoke(HttpMethod method, String url, @Nullable Query query, + @Nullable String jsonCommand) throws HubMaintenanceException, HubProcessingException { if (logger.isTraceEnabled()) { - logger.trace("API command {} {}", jsonCommand == null ? GET : PUT, target.getUri()); + logger.trace("API command {} {}", method, url); if (jsonCommand != null) { logger.trace("JSON command = {}", jsonCommand); } } - Response response; + Request request = httpClient.newRequest(url).method(method).header("Connection", "close").accept("*/*"); + if (query != null) { + request.param(query.getKey(), query.getValue()); + } + if (jsonCommand != null) { + request.header(HttpHeader.CONTENT_TYPE, "application/json").content(new StringContentProvider(jsonCommand)); + } + ContentResponse response; try { - response = invocation.invoke(); - } catch (ProcessingException e) { + response = request.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { if (Instant.now().isBefore(maintenanceScheduledEnd)) { // throw "softer" exception during maintenance window logger.debug("Hub still undergoing maintenance"); throw new HubMaintenanceException("Hub still undergoing maintenance"); } - throw e; + throw new HubProcessingException(String.format("%s: \"%s\"", e.getClass().getName(), e.getMessage())); } int statusCode = response.getStatus(); - if (statusCode == 423) { + if (statusCode == HttpStatus.LOCKED_423) { // set end of maintenance window, and throw a "softer" exception maintenanceScheduledEnd = Instant.now().plusSeconds(maintenancePeriod); logger.debug("Hub undergoing maintenance"); - if (response.hasEntity()) { - response.readEntity(String.class); - } - response.close(); throw new HubMaintenanceException("Hub undergoing maintenance"); } - if (statusCode != 200) { - logger.warn("Hub returned HTTP error '{}'", statusCode); - if (response.hasEntity()) { - response.readEntity(String.class); - } - response.close(); - throw new ProcessingException(String.format("HTTP %d error", statusCode)); + if (statusCode != HttpStatus.OK_200) { + logger.warn("Hub returned HTTP {} {}", statusCode, response.getReason()); + throw new HubProcessingException(String.format("HTTP %d error", statusCode)); } - if (!response.hasEntity()) { + String jsonResponse = response.getContentAsString(); + if ("".equals(jsonResponse)) { logger.warn("Hub returned no content"); - response.close(); - throw new ProcessingException("Missing response entity"); + throw new HubProcessingException("Missing response entity"); } - String jsonResponse = response.readEntity(String.class); if (logger.isTraceEnabled()) { logger.trace("JSON response = {}", jsonResponse); } @@ -195,15 +219,16 @@ private synchronized String invoke(Invocation invocation, WebTarget target, @Nul /** * Fetches a JSON package that describes a specific shade in the hub, and wraps it * in a Shade class instance - * + * * @param shadeId id of the shade to be fetched * @return Shade class instance - * @throws ProcessingException if there is any processing error + * @throws JsonParseException if there is a JSON parsing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public @Nullable Shade getShade(int shadeId) throws ProcessingException, HubMaintenanceException { - WebTarget target = shade.resolveTemplate(ID, shadeId); - String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null); + public @Nullable Shade getShade(int shadeId) + throws JsonParseException, HubProcessingException, HubMaintenanceException { + String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), null, null); return gson.fromJson(json, Shade.class); } @@ -211,30 +236,29 @@ private synchronized String invoke(Invocation invocation, WebTarget target, @Nul * Instructs the hub to do a hard refresh (discovery on the hubs RF network) on * a specific shade; fetches a JSON package that describes that shade, and wraps * it in a Shade class instance - * + * * @param shadeId id of the shade to be refreshed * @return Shade class instance - * @throws ProcessingException if there is any processing error + * @throws JsonParseException if there is a JSON parsing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public @Nullable Shade refreshShade(int shadeId) throws ProcessingException, HubMaintenanceException { - WebTarget target = shade.resolveTemplate(ID, shadeId).queryParam(REFRESH, true); - String json = invoke(target.request().header(CONN_HDR, CONN_VAL).buildGet(), target, null); + public @Nullable Shade refreshShade(int shadeId) + throws JsonParseException, HubProcessingException, HubMaintenanceException { + String json = invoke(HttpMethod.GET, shades + Integer.toString(shadeId), + Query.of("refresh", Boolean.toString(true)), null); return gson.fromJson(json, Shade.class); } /** * Tells the hub to stop movement of a specific shade - * + * * @param shadeId id of the shade to be stopped - * @throws ProcessingException if there is any processing error + * @throws HubProcessingException if there is any processing error * @throws HubMaintenanceException if the hub is down for maintenance */ - public void stopShade(int shadeId) throws ProcessingException, HubMaintenanceException { - WebTarget target = shade.resolveTemplate(ID, shadeId); + public void stopShade(int shadeId) throws HubProcessingException, HubMaintenanceException { String json = gson.toJson(new ShadeStop(shadeId)); - invoke(target.request().header(CONN_HDR, CONN_VAL) - .buildPut(Entity.entity(json, MediaType.APPLICATION_JSON_TYPE)), target, json); - return; + invoke(HttpMethod.PUT, shades + Integer.toString(shadeId), null, json); } } diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HubProcessingException.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HubProcessingException.java new file mode 100644 index 0000000000000..87594d1af770d --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/HubProcessingException.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 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.hdpowerview.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link HubProcessingException} is a custom exception for the HD PowerView hub + * + * @author Andrew Fiddian-Green - Initial contribution + */ +@NonNullByDefault +public class HubProcessingException extends Exception { + + private static final long serialVersionUID = 4307088023775166450L; + + public HubProcessingException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java index e733ff3c41eca..ad4ad4e390cf5 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/discovery/HDPowerViewShadeDiscoveryService.java @@ -17,13 +17,12 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import javax.ws.rs.ProcessingException; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; import org.openhab.binding.hdpowerview.internal.HubMaintenanceException; +import org.openhab.binding.hdpowerview.internal.HubProcessingException; import org.openhab.binding.hdpowerview.internal.api.responses.Shades; import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData; import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration; @@ -85,7 +84,7 @@ private Runnable createScanner() { try { HDPowerViewWebTargets webTargets = hub.getWebTargets(); if (webTargets == null) { - throw new ProcessingException("Web targets not initialized"); + throw new HubProcessingException("Web targets not initialized"); } Shades shades = webTargets.getShades(); if (shades != null && shades.shadeData != null) { @@ -107,7 +106,7 @@ private Runnable createScanner() { } } } - } catch (ProcessingException | JsonParseException e) { + } catch (HubProcessingException | JsonParseException e) { logger.warn("Unexpected error: {}", e.getMessage()); } catch (HubMaintenanceException e) { // exceptions are logged in HDPowerViewWebTargets diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java index b67d4a20d6755..0827a682e31e4 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java @@ -21,13 +21,14 @@ import java.util.concurrent.TimeUnit; import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; import org.openhab.binding.hdpowerview.internal.HubMaintenanceException; +import org.openhab.binding.hdpowerview.internal.HubProcessingException; import org.openhab.binding.hdpowerview.internal.api.responses.Scenes; import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; import org.openhab.binding.hdpowerview.internal.api.responses.Shades; @@ -63,7 +64,7 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler { private final Logger logger = LoggerFactory.getLogger(HDPowerViewHubHandler.class); - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; private long refreshInterval; private long hardRefreshInterval; @@ -75,9 +76,9 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler { private final ChannelTypeUID sceneChannelTypeUID = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID, HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE); - public HDPowerViewHubHandler(Bridge bridge, ClientBuilder clientBuilder) { + public HDPowerViewHubHandler(Bridge bridge, HttpClient httpClient) { super(bridge); - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; } @Override @@ -98,7 +99,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { webTargets.activateScene(Integer.parseInt(channelUID.getId())); } catch (HubMaintenanceException e) { // exceptions are logged in HDPowerViewWebTargets - } catch (NumberFormatException | ProcessingException e) { + } catch (NumberFormatException | HubProcessingException e) { logger.debug("Unexpected error {}", e.getMessage()); } } @@ -116,7 +117,7 @@ public void initialize() { return; } - webTargets = new HDPowerViewWebTargets(clientBuilder.build(), host); + webTargets = new HDPowerViewWebTargets(httpClient, host); refreshInterval = config.refresh; hardRefreshInterval = config.hardRefresh; schedulePoll(); @@ -178,7 +179,7 @@ private synchronized void poll() { pollScenes(); } catch (JsonParseException e) { logger.warn("Bridge returned a bad JSON response: {}", e.getMessage()); - } catch (ProcessingException e) { + } catch (HubProcessingException e) { logger.warn("Error connecting to bridge: {}", e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, e.getMessage()); } catch (HubMaintenanceException e) { @@ -186,7 +187,7 @@ private synchronized void poll() { } } - private void pollShades() throws JsonParseException, ProcessingException, HubMaintenanceException { + private void pollShades() throws JsonParseException, HubProcessingException, HubMaintenanceException { HDPowerViewWebTargets webTargets = this.webTargets; if (webTargets == null) { throw new ProcessingException("Web targets not initialized"); @@ -229,7 +230,7 @@ private void updateShadeThing(String shadeId, Thing thing, @Nullable ShadeData s thingHandler.onReceiveUpdate(shadeData); } - private void pollScenes() throws JsonParseException, ProcessingException, HubMaintenanceException { + private void pollScenes() throws JsonParseException, HubProcessingException, HubMaintenanceException { HDPowerViewWebTargets webTargets = this.webTargets; if (webTargets == null) { throw new ProcessingException("Web targets not initialized"); diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java index 5bbf5fa294c0d..3856de9d7561f 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewShadeHandler.java @@ -19,12 +19,11 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import javax.ws.rs.ProcessingException; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; import org.openhab.binding.hdpowerview.internal.HubMaintenanceException; +import org.openhab.binding.hdpowerview.internal.HubProcessingException; import org.openhab.binding.hdpowerview.internal.api.ActuatorClass; import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem; import org.openhab.binding.hdpowerview.internal.api.ShadePosition; @@ -128,7 +127,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { /** * Update the state of the channels based on the ShadeData provided - * + * * @param shadeData the ShadeData to be used; may be null */ protected void onReceiveUpdate(@Nullable ShadeData shadeData) { @@ -157,11 +156,11 @@ private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, i try { HDPowerViewHubHandler bridge; if ((bridge = getBridgeHandler()) == null) { - throw new ProcessingException("Missing bridge handler"); + throw new HubProcessingException("Missing bridge handler"); } HDPowerViewWebTargets webTargets = bridge.getWebTargets(); if (webTargets == null) { - throw new ProcessingException("Web targets not initialized"); + throw new HubProcessingException("Web targets not initialized"); } int shadeId = getShadeId(); @@ -190,7 +189,7 @@ private void moveShade(ActuatorClass actuatorClass, CoordinateSystem coordSys, i webTargets.moveShade(shadeId, ShadePosition.create(ZERO_IS_CLOSED, primaryPercent, ZERO_IS_OPEN, newPercent)); } - } catch (ProcessingException | NumberFormatException e) { + } catch (HubProcessingException | NumberFormatException e) { logger.warn("Unexpected error: {}", e.getMessage()); return; } catch (HubMaintenanceException e) { @@ -211,16 +210,16 @@ private void stopShade() { try { HDPowerViewHubHandler bridge; if ((bridge = getBridgeHandler()) == null) { - throw new ProcessingException("Missing bridge handler"); + throw new HubProcessingException("Missing bridge handler"); } HDPowerViewWebTargets webTargets = bridge.getWebTargets(); if (webTargets == null) { - throw new ProcessingException("Web targets not initialized"); + throw new HubProcessingException("Web targets not initialized"); } int shadeId = getShadeId(); webTargets.stopShade(shadeId); requestRefreshShade(); - } catch (ProcessingException | NumberFormatException e) { + } catch (HubProcessingException | NumberFormatException e) { logger.warn("Unexpected error: {}", e.getMessage()); return; } catch (HubMaintenanceException e) { @@ -242,11 +241,11 @@ private void doRefreshShade() { try { HDPowerViewHubHandler bridge; if ((bridge = getBridgeHandler()) == null) { - throw new ProcessingException("Missing bridge handler"); + throw new HubProcessingException("Missing bridge handler"); } HDPowerViewWebTargets webTargets = bridge.getWebTargets(); if (webTargets == null) { - throw new ProcessingException("Web targets not initialized"); + throw new HubProcessingException("Web targets not initialized"); } int shadeId = getShadeId(); Shade shade = webTargets.refreshShade(shadeId); @@ -258,7 +257,7 @@ private void doRefreshShade() { } } } - } catch (ProcessingException | NumberFormatException e) { + } catch (HubProcessingException | NumberFormatException e) { logger.warn("Unexpected error: {}", e.getMessage()); } catch (HubMaintenanceException e) { // exceptions are logged in HDPowerViewWebTargets diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java index cb0d41910896a..7f6891dd3d281 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/HDPowerViewJUnitTests.java @@ -22,15 +22,13 @@ import java.util.List; import java.util.regex.Pattern; -import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.Test; import org.openhab.binding.hdpowerview.internal.HDPowerViewWebTargets; import org.openhab.binding.hdpowerview.internal.HubMaintenanceException; +import org.openhab.binding.hdpowerview.internal.HubProcessingException; import org.openhab.binding.hdpowerview.internal.api.CoordinateSystem; import org.openhab.binding.hdpowerview.internal.api.ShadePosition; import org.openhab.binding.hdpowerview.internal.api.responses.Scenes; @@ -76,7 +74,7 @@ private String loadJson(String fileName) { /** * Run a series of ONLINE tests on the communication with a hub - * + * * @param hubIPAddress must be a valid hub IP address to run the * tests on; or an INVALID IP address to * suppress the tests @@ -99,10 +97,18 @@ public void testOnlineCommunication() { boolean allowShadeMovementCommands = false; if (VALID_IP_V4_ADDRESS.matcher(hubIPAddress).matches()) { - // initialize stuff - Client client = ClientBuilder.newClient(); + // ==== initialize stuff ==== + HttpClient client = new HttpClient(); assertNotNull(client); - // client.register(new Logger()); + + // ==== start the client ==== + try { + client.start(); + assertTrue(client.isStarted()); + } catch (Exception e) { + fail(e.getMessage()); + } + HDPowerViewWebTargets webTargets = new HDPowerViewWebTargets(client, hubIPAddress); assertNotNull(webTargets); @@ -180,7 +186,7 @@ public void testOnlineCommunication() { String shadeName = shadexData.getName(); assertNotNull(shadeName); } - } catch (JsonParseException | ProcessingException | HubMaintenanceException e) { + } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) { fail(e.getMessage()); } @@ -203,7 +209,7 @@ public void testOnlineCommunication() { String sceneName = scene.getName(); assertNotNull(sceneName); } - } catch (JsonParseException | ProcessingException | HubMaintenanceException e) { + } catch (JsonParseException | HubProcessingException | HubMaintenanceException e) { fail(e.getMessage()); } @@ -214,7 +220,7 @@ public void testOnlineCommunication() { assertNotEquals(0, shadeId); shade = webTargets.refreshShade(shadeId); assertNotNull(shade); - } catch (ProcessingException | HubMaintenanceException e) { + } catch (HubProcessingException | HubMaintenanceException e) { fail(e.getMessage()); } @@ -245,7 +251,7 @@ public void testOnlineCommunication() { if (allowShadeMovementCommands) { webTargets.moveShade(shadeId, newPos); } - } catch (ProcessingException | HubMaintenanceException e) { + } catch (HubProcessingException | HubMaintenanceException e) { fail(e.getMessage()); } @@ -254,7 +260,26 @@ public void testOnlineCommunication() { try { assertNotNull(sceneId); webTargets.activateScene(sceneId); - } catch (ProcessingException | HubMaintenanceException e) { + } catch (HubProcessingException | HubMaintenanceException e) { + fail(e.getMessage()); + } + } + + // ==== test stop command ==== + if (allowShadeMovementCommands) { + try { + assertNotNull(sceneId); + webTargets.stopShade(shadeId); + } catch (HubProcessingException | HubMaintenanceException e) { + fail(e.getMessage()); + } + } + + // ==== stop the client ==== + if (client.isRunning()) { + try { + client.stop(); + } catch (Exception e) { fail(e.getMessage()); } } From 7abeb973961a09007e67105c4427f1631bf58236 Mon Sep 17 00:00:00 2001 From: Pete <8108165+PRosenb@users.noreply.github.com> Date: Tue, 16 Feb 2021 21:26:34 +0100 Subject: [PATCH 002/118] [bluetooth.generic] Enable BLE notification for linked channels (#10122) * [bluetooth] Add BluetoothDevice.isNotifying() * [bluetooth] Improve Characteristic properties support * [bluez] Improve Characteristic properties support * [bluetooth] Add BluetoothDevice.canNotify() * [bluez] Also catch DBusExecutionException on read value * [bluetooth.generic] Activate notifications for linked channels where characteristics are able to notify * [bluez] Adjust javadoc * [bluegiga] Add BluetoothDevice.isNotifying() support * [bluegiga] Fix notification enabled check * [bluetooth] move canNotify() to Characteristic * [bluegiga] rename notificationEnabled to notifying * [bluetooth.generic] use handlerToChannels to subscribe to notifications * [bluetooth.generic] implement TODOs of canRead()/canWrite() * [bluetooth.generic] optimize ChannelUID * [bluetooth.generic] use channelUids for link check Signed-off-by: Peter Rosenberg --- .../BlueGigaBluetoothCharacteristic.java | 10 +- .../bluegiga/BlueGigaBluetoothDevice.java | 18 ++-- .../bluez/internal/BlueZBluetoothDevice.java | 53 ++++++++- .../internal/GenericBluetoothHandler.java | 38 +++++-- .../bluetooth/BluetoothCharacteristic.java | 51 +++++++++ .../binding/bluetooth/BluetoothDevice.java | 10 ++ .../bluetooth/DelegateBluetoothDevice.java | 6 ++ .../CharacteristicPropertiesTest.java | 101 ++++++++++++++++++ .../bluetooth/MockBluetoothDevice.java | 5 + 9 files changed, 270 insertions(+), 22 deletions(-) create mode 100644 bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java index afa8ba4843b83..43206c9b1c37f 100644 --- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java +++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothCharacteristic.java @@ -27,7 +27,7 @@ */ public class BlueGigaBluetoothCharacteristic extends BluetoothCharacteristic { - private boolean notificationEnabled; + private boolean notifying; public BlueGigaBluetoothCharacteristic(int handle) { super(null, handle); @@ -45,11 +45,11 @@ public void setUUID(UUID uuid) { this.uuid = uuid; } - public boolean isNotificationEnabled() { - return notificationEnabled; + public boolean isNotifying() { + return notifying; } - public void setNotificationEnabled(boolean enable) { - this.notificationEnabled = enable; + public void setNotifying(boolean enable) { + this.notifying = enable; } } diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java index c2cb969e8fe4e..ccff259a0b878 100644 --- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java @@ -196,7 +196,7 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { } BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; - if (ch.isNotificationEnabled()) { + if (ch.isNotifying()) { return true; } @@ -241,12 +241,12 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { @Override public boolean disableNotifications(BluetoothCharacteristic characteristic) { if (connection == -1) { - logger.debug("Cannot enable notifications, device not connected {}", this); + logger.debug("Cannot disable notifications, device not connected {}", this); return false; } BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; - if (ch.isNotificationEnabled()) { + if (!ch.isNotifying()) { return true; } @@ -288,6 +288,12 @@ public boolean disableNotifications(BluetoothCharacteristic characteristic) { return true; } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + BlueGigaBluetoothCharacteristic ch = (BlueGigaBluetoothCharacteristic) characteristic; + return ch.isNotifying(); + } + @Override public boolean enableNotifications(BluetoothDescriptor descriptor) { // TODO will be implemented in a followup PR @@ -613,7 +619,7 @@ private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event if (!success) { logger.debug("write to descriptor failed"); } - ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(success); + ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(success); procedureProgress = BlueGigaProcedure.NONE; procedureCharacteristic = null; break; @@ -622,7 +628,7 @@ private void handleProcedureCompletedEvent(BlueGigaProcedureCompletedEvent event if (!success) { logger.debug("write to descriptor failed"); } - ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotificationEnabled(!success); + ((BlueGigaBluetoothCharacteristic) procedureCharacteristic).setNotifying(!success); procedureProgress = BlueGigaProcedure.NONE; procedureCharacteristic = null; break; @@ -656,7 +662,7 @@ private void handleDisconnectedEvent(BlueGigaDisconnectedEvent event) { } for (BlueGigaBluetoothCharacteristic ch : handleToCharacteristic.values()) { - ch.setNotificationEnabled(false); + ch.setNotifying(false); } cancelTimer(procedureTimer); diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java index f4d78ab591e64..509f21fa429a1 100644 --- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/internal/BlueZBluetoothDevice.java @@ -57,6 +57,7 @@ * * @author Kai Kreuzer - Initial contribution and API * @author Benjamin Lafois - Replaced tinyB with bluezDbus + * @author Peter Rosenberg - Improve notifications and properties support * */ @NonNullByDefault @@ -399,6 +400,7 @@ public boolean discoverServices() { for (BluetoothGattCharacteristic dBusBlueZCharacteristic : dBusBlueZService.getGattCharacteristics()) { BluetoothCharacteristic characteristic = new BluetoothCharacteristic( UUID.fromString(dBusBlueZCharacteristic.getUuid()), 0); + convertCharacteristicProperties(dBusBlueZCharacteristic, characteristic); for (BluetoothGattDescriptor dBusBlueZDescriptor : dBusBlueZCharacteristic.getGattDescriptors()) { BluetoothDescriptor descriptor = new BluetoothDescriptor(characteristic, @@ -414,6 +416,42 @@ public boolean discoverServices() { return true; } + /** + * Convert the flags of BluetoothGattCharacteristic to the int bitset used by BluetoothCharacteristic. + * + * @param dBusBlueZCharacteristic source characteristic to read the flags from + * @param characteristic destination characteristic to write to properties to + */ + private void convertCharacteristicProperties(BluetoothGattCharacteristic dBusBlueZCharacteristic, + BluetoothCharacteristic characteristic) { + int properties = 0; + + for (String property : dBusBlueZCharacteristic.getFlags()) { + switch (property) { + case "broadcast": + properties |= BluetoothCharacteristic.PROPERTY_BROADCAST; + break; + case "read": + properties |= BluetoothCharacteristic.PROPERTY_READ; + break; + case "write-without-response": + properties |= BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE; + break; + case "write": + properties |= BluetoothCharacteristic.PROPERTY_WRITE; + break; + case "notify": + properties |= BluetoothCharacteristic.PROPERTY_NOTIFY; + break; + case "indicate": + properties |= BluetoothCharacteristic.PROPERTY_INDICATE; + break; + } + } + + characteristic.setProperties(properties); + } + @Override public boolean readCharacteristic(BluetoothCharacteristic characteristic) { BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString()); @@ -428,7 +466,8 @@ public boolean readCharacteristic(BluetoothCharacteristic characteristic) { characteristic.setValue(value); notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, BluetoothCompletionStatus.SUCCESS); - } catch (DBusException e) { + } catch (DBusException | DBusExecutionException e) { + // DBusExecutionException is thrown if the value cannot be read logger.debug("Exception occurred when trying to read characteristic '{}': {}", characteristic.getUuid(), e.getMessage()); notifyListeners(BluetoothEventType.CHARACTERISTIC_READ_COMPLETE, characteristic, @@ -438,6 +477,18 @@ public boolean readCharacteristic(BluetoothCharacteristic characteristic) { return true; } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString()); + if (c != null) { + Boolean isNotifying = c.isNotifying(); + return Objects.requireNonNullElse(isNotifying, false); + } else { + logger.warn("Characteristic '{}' is missing on device '{}'.", characteristic.getUuid(), address); + return false; + } + } + @Override public boolean disableNotifications(BluetoothCharacteristic characteristic) { BluetoothGattCharacteristic c = getDBusBlueZCharacteristicByUUID(characteristic.getUuid().toString()); diff --git a/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java b/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java index 5717b08210729..633170f4284e4 100644 --- a/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java +++ b/bundles/org.openhab.binding.bluetooth.generic/src/main/java/org/openhab/binding/bluetooth/generic/internal/GenericBluetoothHandler.java @@ -58,6 +58,7 @@ * channels based off of a bluetooth device's GATT characteristics. * * @author Connor Petty - Initial contribution + * @author Peter Rosenberg - Use notifications * */ @NonNullByDefault @@ -68,6 +69,7 @@ public class GenericBluetoothHandler extends ConnectedBluetoothHandler { private final Map channelHandlers = new ConcurrentHashMap<>(); private final BluetoothGattParser gattParser = BluetoothGattParserFactory.getDefault(); private final CharacteristicChannelTypeProvider channelTypeProvider; + private final Map> handlerToChannels = new ConcurrentHashMap<>(); private @Nullable ScheduledFuture readCharacteristicJob = null; @@ -84,12 +86,14 @@ public void initialize() { readCharacteristicJob = scheduler.scheduleWithFixedDelay(() -> { if (device.getConnectionState() == ConnectionState.CONNECTED) { if (resolved) { - for (CharacteristicHandler charHandler : charHandlers.values()) { - if (charHandler.canRead()) { + handlerToChannels.forEach((charHandler, channelUids) -> { + // Only read the value manually if notification is not on. + // Also read it the first time before we activate notifications below. + if (!device.isNotifying(charHandler.characteristic) && charHandler.canRead()) { device.readCharacteristic(charHandler.characteristic); try { // TODO the ideal solution would be to use locks/conditions and timeouts - // between this code and `onCharacteristicReadComplete` but + // Kbetween this code and `onCharacteristicReadComplete` but // that would overcomplicate the code a bit and I plan // on implementing a better more generalized solution later Thread.sleep(50); @@ -97,7 +101,20 @@ public void initialize() { return; } } - } + if (charHandler.characteristic.canNotify()) { + // Enabled/Disable notifications dependent on if the channel is linked. + // TODO check why isLinked() is true for not linked channels + if (channelUids.stream().anyMatch(this::isLinked)) { + if (!device.isNotifying(charHandler.characteristic)) { + device.enableNotifications(charHandler.characteristic); + } + } else { + if (device.isNotifying(charHandler.characteristic)) { + device.disableNotifications(charHandler.characteristic); + } + } + } + }); } else { // if we are connected and still haven't been able to resolve the services, try disconnecting and // then connecting again @@ -117,6 +134,7 @@ public void dispose() { charHandlers.clear(); channelHandlers.clear(); + handlerToChannels.clear(); } @Override @@ -161,9 +179,11 @@ private void updateThingChannels() { logger.trace("{} processing characteristic {}", address, characteristic.getUuid()); CharacteristicHandler handler = getCharacteristicHandler(characteristic); List chans = handler.buildChannels(); - for (Channel channel : chans) { - channelHandlers.put(channel.getUID(), handler); + List chanUids = chans.stream().map(Channel::getUID).collect(Collectors.toList()); + for (ChannelUID channel : chanUids) { + channelHandlers.put(channel, handler); } + handlerToChannels.put(handler, chanUids); return chans.stream(); })// .collect(Collectors.toList()); @@ -341,8 +361,7 @@ public boolean canRead() { if (gattParser.isKnownCharacteristic(charUUID)) { return gattParser.isValidForRead(charUUID); } - // TODO: need to evaluate this from characteristic properties, but such properties aren't support yet - return true; + return characteristic.canRead(); } public boolean canWrite() { @@ -350,8 +369,7 @@ public boolean canWrite() { if (gattParser.isKnownCharacteristic(charUUID)) { return gattParser.isValidForWrite(charUUID); } - // TODO: need to evaluate this from characteristic properties, but such properties aren't support yet - return true; + return characteristic.canWrite(); } private boolean isAdvanced() { diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java index 0a014ecb36e93..dd591acc723f6 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothCharacteristic.java @@ -31,6 +31,7 @@ * * @author Chris Jackson - Initial contribution * @author Kai Kreuzer - Cleaned up code + * @author Peter Rosenberg - Improve properties support */ public class BluetoothCharacteristic { public static final int FORMAT_UINT8 = 0x11; @@ -142,6 +143,16 @@ public int getInstanceId() { return instance; } + /** + * Set the raw properties. The individual properties are represented as bits inside + * of this int value. + * + * @param properties of this Characteristic + */ + public void setProperties(int properties) { + this.properties = properties; + } + /** * Returns the properties of this characteristic. * @@ -152,6 +163,46 @@ public int getProperties() { return properties; } + /** + * Returns if the given characteristics property is enabled or not. + * + * @param property one of the Constants BluetoothCharacteristic.PROPERTY_XX + * @return true if this characteristic has the given property enabled, false if properties not set or + * the given property is not enabled. + */ + public boolean hasPropertyEnabled(int property) { + return (properties & property) != 0; + } + + /** + * Returns if notifications can be enabled on this characteristic. + * + * @return true if notifications can be enabled, false if notifications are not supported, characteristic is missing + * on device or notifications are not supported. + */ + public boolean canNotify() { + return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY); + } + + /** + * Returns if the value can be read on this characteristic. + * + * @return true if the value can be read, false otherwise. + */ + public boolean canRead() { + return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ); + } + + /** + * Returns if the value can be written on this characteristic. + * + * @return true if the value can be written with of without a response, false otherwise. + */ + public boolean canWrite() { + return hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE) + || hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE); + } + /** * Returns the permissions for this characteristic. */ diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java index f9dfde0862b5d..ba1186829f12b 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java @@ -28,6 +28,7 @@ * @author Chris Jackson - Initial contribution * @author Kai Kreuzer - Refactored class to use Integer instead of int, fixed bugs, diverse improvements * @author Connor Petty - Made most of the methods abstract + * @author Peter Rosenberg - Improve notifications */ @NonNullByDefault public abstract class BluetoothDevice { @@ -249,6 +250,15 @@ public BluetoothAdapter getAdapter() { */ public abstract boolean writeCharacteristic(BluetoothCharacteristic characteristic); + /** + * Returns if notification is enabled for the given characteristic. + * + * @param characteristic the {@link BluetoothCharacteristic} to check if notifications are enabled. + * @return true if notification is enabled, false if notification is disabled, characteristic is missing on device + * or notifications are not supported. + */ + public abstract boolean isNotifying(BluetoothCharacteristic characteristic); + /** * Enables notifications for a characteristic. Only a single read or write operation can be requested at once. * Attempting to perform an operation when one is already in progress will result in subsequent calls returning diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java index bb4c37029bf1b..074d9a8f39e7e 100644 --- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/DelegateBluetoothDevice.java @@ -112,6 +112,12 @@ public boolean writeCharacteristic(BluetoothCharacteristic characteristic) { return delegate != null && delegate.writeCharacteristic(characteristic); } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + BluetoothDevice delegate = getDelegate(); + return delegate != null ? delegate.isNotifying(characteristic) : false; + } + @Override public boolean enableNotifications(BluetoothCharacteristic characteristic) { BluetoothDevice delegate = getDelegate(); diff --git a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java new file mode 100644 index 0000000000000..3d40537f6d550 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/CharacteristicPropertiesTest.java @@ -0,0 +1,101 @@ +/** + * Copyright (c) 2010-2021 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.bluetooth; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.UUID; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link BluetoothCharacteristic}. + * + * @author Peter Rosenberg - Initial contribution + */ +public class CharacteristicPropertiesTest { + private BluetoothCharacteristic characteristic = new BluetoothCharacteristic(UUID.randomUUID(), 0); + + @Test + public void testAllSupportedProperties() { + // given + // when + int properties = 0; + properties |= BluetoothCharacteristic.PROPERTY_BROADCAST; + properties |= BluetoothCharacteristic.PROPERTY_READ; + properties |= BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE; + properties |= BluetoothCharacteristic.PROPERTY_WRITE; + properties |= BluetoothCharacteristic.PROPERTY_NOTIFY; + properties |= BluetoothCharacteristic.PROPERTY_INDICATE; + characteristic.setProperties(properties); + + // then + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST), "Broastcast not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE), + "Write not response not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE), + "Signed write set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS), + "Extended props set"); + } + + @Test + public void testNoProperties() { + // given + // when + int properties = 0; + characteristic.setProperties(properties); + + // then + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST), + "Broastcast not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE), + "Write not response not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE), + "Signed write set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS), + "Extended props set"); + } + + @Test + public void testSomeSupportedProperties() { + // given + // when + int properties = 0; + properties |= BluetoothCharacteristic.PROPERTY_READ; + properties |= BluetoothCharacteristic.PROPERTY_NOTIFY; + characteristic.setProperties(properties); + + // then + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_BROADCAST), + "Broastcast not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_READ), "Read not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE_NO_RESPONSE), + "Write not response not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_WRITE), "Write not set"); + assertTrue(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_NOTIFY), "Notify not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_INDICATE), "Indicate not set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_SIGNED_WRITE), + "Signed write set"); + assertFalse(characteristic.hasPropertyEnabled(BluetoothCharacteristic.PROPERTY_EXTENDED_PROPS), + "Extended props set"); + } +} diff --git a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java index d5170cd071df5..892362f86c1d2 100644 --- a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java +++ b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothDevice.java @@ -102,6 +102,11 @@ public boolean enableNotifications(BluetoothCharacteristic characteristic) { return false; } + @Override + public boolean isNotifying(BluetoothCharacteristic characteristic) { + return false; + } + @Override public boolean disableNotifications(BluetoothCharacteristic characteristic) { return false; From 6f9d80ea9e3463eeaca784c76a13aa32113f2c59 Mon Sep 17 00:00:00 2001 From: Ethan Dye Date: Tue, 16 Feb 2021 13:48:07 -0700 Subject: [PATCH 003/118] [OmniLink] Fix OmniLink temperature/humidity sensors (#10130) Signed-off-by: Ethan Dye --- bundles/org.openhab.binding.omnilink/pom.xml | 2 +- .../omnilink/internal/handler/HumiditySensorHandler.java | 4 ++-- .../omnilink/internal/handler/TempSensorHandler.java | 4 ++-- .../omnilink/internal/handler/TemperatureFormat.java | 6 +++--- .../omnilink/internal/handler/ThermostatHandler.java | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.omnilink/pom.xml b/bundles/org.openhab.binding.omnilink/pom.xml index ee0e2767fb4ae..07e400f6ccd16 100644 --- a/bundles/org.openhab.binding.omnilink/pom.xml +++ b/bundles/org.openhab.binding.omnilink/pom.xml @@ -18,7 +18,7 @@ com.github.digitaldan jomnilink - 1.4.0 + 1.4.1 compile diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java index bda8fe7df6112..a0751d68b7fa3 100644 --- a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java +++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java @@ -113,12 +113,12 @@ public void handleCommand(ChannelUID channelUID, Command command) { switch (channelUID.getId()) { case CHANNEL_AUX_LOW_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(), - TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()), + TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).floatValue()), thingID); break; case CHANNEL_AUX_HIGH_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(), - TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()), + TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).floatValue()), thingID); break; default: diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java index 850e2ad99c4c3..7152841ae0076 100644 --- a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java +++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java @@ -126,12 +126,12 @@ public void handleCommand(ChannelUID channelUID, Command command) { switch (channelUID.getId()) { case CHANNEL_AUX_LOW_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(), - temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()), + temperatureFormat.get().formatToOmni(((QuantityType) command).floatValue()), thingID); break; case CHANNEL_AUX_HIGH_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(), - temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()), + temperatureFormat.get().formatToOmni(((QuantityType) command).floatValue()), thingID); break; default: diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java index 8379e0dfb2ef6..fdff35def4a48 100644 --- a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java +++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java @@ -33,7 +33,7 @@ public float omniToFormat(int omniNumber) { } @Override - public int formatToOmni(int celsius) { + public int formatToOmni(float celsius) { return MessageUtils.CToOmni(celsius); } }, @@ -44,7 +44,7 @@ public float omniToFormat(int omniNumber) { } @Override - public int formatToOmni(int fahrenheit) { + public int formatToOmni(float fahrenheit) { return MessageUtils.FtoOmni(fahrenheit); } }; @@ -69,7 +69,7 @@ private TemperatureFormat(int formatNumber) { * @param format Number in the current format. * @return Omni formatted number. */ - public abstract int formatToOmni(int format); + public abstract int formatToOmni(float format); /** * Get the number which identifies this format as defined by the omniprotocol. diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java index 37fb57f2f7a6d..f5daccab31857 100644 --- a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java +++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java @@ -157,22 +157,22 @@ public void handleCommand(ChannelUID channelUID, Command command) { break; case CHANNEL_THERMO_HEAT_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(), - temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()), + temperatureFormat.get().formatToOmni(((QuantityType) command).floatValue()), thingID); break; case CHANNEL_THERMO_COOL_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(), - temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()), + temperatureFormat.get().formatToOmni(((QuantityType) command).floatValue()), thingID); break; case CHANNEL_THERMO_HUMIDIFY_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HUMDIFY_POINT.getNumber(), - TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()), + TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).floatValue()), thingID); break; case CHANNEL_THERMO_DEHUMIDIFY_SETPOINT: sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_DEHUMIDIFY_POINT.getNumber(), - TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()), + TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).floatValue()), thingID); break; default: From 4495c53f7e5a9f91f39b87351ab7436ac364a183 Mon Sep 17 00:00:00 2001 From: Eiko Wagenknecht Date: Wed, 17 Feb 2021 00:28:11 +0100 Subject: [PATCH 004/118] [homematic] [doc] Usage of custom ID not supported (#10131) Fixes https://github.com/openhab/openhab-addons/issues/10114 Signed-off-by: Eiko Wagenknecht --- bundles/org.openhab.binding.homematic/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.homematic/README.md b/bundles/org.openhab.binding.homematic/README.md index 7eaf099bc175b..d0724f662fe21 100644 --- a/bundles/org.openhab.binding.homematic/README.md +++ b/bundles/org.openhab.binding.homematic/README.md @@ -289,10 +289,12 @@ Dimmer Light "Light [%d %%]" { channel="homematic:HM-LC-Dim1T-Pl-2:cc The GATEWAY-EXTRAS is a virtual device which contains a switch to reload all values from all devices and also a switch to put the gateway in the install mode to add new devices. If the gateway supports variables and scripts, you can handle them with this device too. + The type is generated: `GATEWAY-EXTRAS-[BRIDGE_ID]`. +Example: bridgeId=**ccu** -> type=GATEWAY-EXTRAS-**CCU** -**Example:** bridgeId=ccu, type=GATEWAY-EXTRAS-CCU -Address: fixed GWE00000000 +The address of the virtual device must be the default value `GWE00000000`. +Usage of a custom ID is not supported. ### RELOAD_ALL_FROM_GATEWAY From fe496c73893cc05d2dad4b1e8297fc66be4fb7dc Mon Sep 17 00:00:00 2001 From: lolodomo Date: Wed, 17 Feb 2021 11:38:27 +0100 Subject: [PATCH 005/118] [rotel] Fix command to select PC USB source (ASCII v2 mode) (#10165) Signed-off-by: Laurent Garnier --- .../rotel/internal/communication/RotelCommand.java | 6 +++--- .../binding/rotel/internal/handler/RotelHandler.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java index 3333ef96d6205..a454430847601 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java @@ -87,7 +87,7 @@ public enum RotelCommand { SOURCE_VIDEO8("Source Video 8", "video8", "video8"), SOURCE_PHONO("Source Phono", RotelConnector.PRIMARY_CMD, (byte) 0x35, "phono", "phono"), SOURCE_USB("Source Front USB", RotelConnector.PRIMARY_CMD, (byte) 0x8E, "usb", "usb"), - SOURCE_PCUSB("Source PC USB", "pc_usb", "pc_usb"), + SOURCE_PCUSB("Source PC USB", "pc_usb", "pcusb"), SOURCE_MULTI_INPUT("Source Multi Input", RotelConnector.PRIMARY_CMD, (byte) 0x15, "multi_input", "multi_input"), SOURCE_AUX("Source Aux", "aux", "aux"), SOURCE_AUX1("Source Aux 1", "aux1", "aux1"), @@ -336,9 +336,9 @@ public byte getHexKey() { * * @throws RotelException - If no command is associated to the searched textual command */ - public static RotelCommand getFromAsciiCommandV2(String text) throws RotelException { + public static RotelCommand getFromAsciiCommand(String text) throws RotelException { for (RotelCommand value : RotelCommand.values()) { - if (text.equals(value.getAsciiCommandV2())) { + if (text.equals(value.getAsciiCommandV1()) || text.equals(value.getAsciiCommandV2())) { return value; } } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java index 943b82fe3bfd8..d2fb766920bbf 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java @@ -1293,28 +1293,28 @@ public void onNewMessageEvent(EventObject event) { updateChannelState(CHANNEL_MAIN_TREBLE); break; case RotelConnector.KEY_SOURCE: - source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommandV2(value)); + source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_SOURCE); updateChannelState(CHANNEL_MAIN_SOURCE); break; case RotelConnector.KEY_RECORD: recordSource = connector.getModel() - .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommandV2(value)); + .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_MAIN_RECORD_SOURCE); break; case RotelConnector.KEY_SOURCE_ZONE2: sourceZone2 = connector.getModel() - .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommandV2(value)); + .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE2_SOURCE); break; case RotelConnector.KEY_SOURCE_ZONE3: sourceZone3 = connector.getModel() - .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommandV2(value)); + .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE3_SOURCE); break; case RotelConnector.KEY_SOURCE_ZONE4: sourceZone4 = connector.getModel() - .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommandV2(value)); + .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE4_SOURCE); break; case RotelConnector.KEY_DSP_MODE: From fbf55d5886a4a6440645acd2768a5d1a4ecd93e5 Mon Sep 17 00:00:00 2001 From: Sami Salonen Date: Wed, 17 Feb 2021 12:41:40 +0200 Subject: [PATCH 006/118] [modbus] fix defaults for tcp and serial things and some other minor cleanup (#10147) * [modbus] More strict nullness. Remove apache.commons.lang from itests * [modbus] Defaults for tcp and serial things according to docs * [modbus] further explicit defaults * [modbus] document default encoding for serial. RTU is pretty much the only one used in the field. Previous default was ascii implicitly. * [modbus] verify defaults are used for undefined configuration parameters Signed-off-by: Sami Salonen --- bundles/org.openhab.binding.modbus/README.md | 2 +- .../config/ModbusSerialConfiguration.java | 16 ++++----- .../config/ModbusTcpConfiguration.java | 8 ++--- .../AbstractModbusEndpointThingHandler.java | 2 +- .../itest.bndrun | 2 -- .../modbus/tests/AbstractModbusOSGiTest.java | 2 +- .../tests/ModbusTcpThingHandlerTest.java | 33 ++++++++++++++++++- 7 files changed, 47 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.modbus/README.md b/bundles/org.openhab.binding.modbus/README.md index c6f561aa443c1..ae30bbe53e4f2 100644 --- a/bundles/org.openhab.binding.modbus/README.md +++ b/bundles/org.openhab.binding.modbus/README.md @@ -146,7 +146,7 @@ Basic parameters | stopBits | text | ✓ | | Stop bits. Valid values are: `"1.0"`, `"1.5"`, `"2.0"`. | | | parity | text | ✓ | | Parity. Valid values are: `"none"`, `"even"`, `"odd"`. | | | dataBits | integer | ✓ | | Data bits. Valid values are: `5`, `6`, `7` and `8`. | | -| encoding | text | ✓ | | Encoding. Valid values are: `"ascii"`, `"rtu"`, `"bin"`. | | +| encoding | text | | `"rtu"` | Encoding. Valid values are: `"ascii"`, `"rtu"`, `"bin"`. | | | echo | boolean | | `false` | Flag for setting the RS485 echo mode. This controls whether we should try to read back whatever we send on the line, before reading the response. Valid values are: `true`, `false`. | | Advanced parameters diff --git a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusSerialConfiguration.java b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusSerialConfiguration.java index 32804d38c4ecc..8e81973a0ec52 100644 --- a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusSerialConfiguration.java +++ b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusSerialConfiguration.java @@ -24,19 +24,19 @@ @NonNullByDefault public class ModbusSerialConfiguration { private @Nullable String port; - private int id; + private int id = 1; private int baud; private @Nullable String stopBits; private @Nullable String parity; private int dataBits; - private @Nullable String encoding; + private String encoding = "rtu"; private boolean echo; - private int receiveTimeoutMillis; - private @Nullable String flowControlIn; - private @Nullable String flowControlOut; - private int timeBetweenTransactionsMillis; - private int connectMaxTries; - private int connectTimeoutMillis; + private int receiveTimeoutMillis = 1500; + private String flowControlIn = "none"; + private String flowControlOut = "none"; + private int timeBetweenTransactionsMillis = 35; + private int connectMaxTries = 1; + private int connectTimeoutMillis = 10_000; private boolean enableDiscovery; public @Nullable String getPort() { diff --git a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusTcpConfiguration.java b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusTcpConfiguration.java index 28ff1d2a2c991..f565f2082a58f 100644 --- a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusTcpConfiguration.java +++ b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/config/ModbusTcpConfiguration.java @@ -25,12 +25,12 @@ public class ModbusTcpConfiguration { private @Nullable String host; private int port; - private int id; - private int timeBetweenTransactionsMillis; + private int id = 1; + private int timeBetweenTransactionsMillis = 60; private int timeBetweenReconnectMillis; - private int connectMaxTries; + private int connectMaxTries = 1; private int reconnectAfterMillis; - private int connectTimeoutMillis; + private int connectTimeoutMillis = 10_000; private boolean enableDiscovery; private boolean rtuEncoded; diff --git a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/handler/AbstractModbusEndpointThingHandler.java b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/handler/AbstractModbusEndpointThingHandler.java index ceb2e7f8f5b47..35e6dd1c605db 100644 --- a/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/handler/AbstractModbusEndpointThingHandler.java +++ b/bundles/org.openhab.binding.modbus/src/main/java/org/openhab/binding/modbus/internal/handler/AbstractModbusEndpointThingHandler.java @@ -45,7 +45,7 @@ public abstract class AbstractModbusEndpointThingHandler addedItems = new HashSet<>(); private Set addedThings = new HashSet<>(); private Set addedLinks = new HashSet<>(); diff --git a/itests/org.openhab.binding.modbus.tests/src/main/java/org/openhab/binding/modbus/tests/ModbusTcpThingHandlerTest.java b/itests/org.openhab.binding.modbus.tests/src/main/java/org/openhab/binding/modbus/tests/ModbusTcpThingHandlerTest.java index 67c590ae82c21..25a8a03e6541a 100644 --- a/itests/org.openhab.binding.modbus.tests/src/main/java/org/openhab/binding/modbus/tests/ModbusTcpThingHandlerTest.java +++ b/itests/org.openhab.binding.modbus.tests/src/main/java/org/openhab/binding/modbus/tests/ModbusTcpThingHandlerTest.java @@ -14,7 +14,7 @@ import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; import java.util.Objects; @@ -46,6 +46,7 @@ private static BridgeBuilder createTcpThingBuilder(String id) { @Test public void testInitializeAndSlaveEndpoint() throws EndpointNotInitializedException { + // Using mocked modbus manager Configuration thingConfig = new Configuration(); thingConfig.put("host", "thisishost"); thingConfig.put("port", 44); @@ -81,6 +82,8 @@ public void testInitializeAndSlaveEndpoint() throws EndpointNotInitializedExcept @Test public void testTwoDifferentEndpointWithDifferentParameters() { + // Real implementation needed to validate this behaviour + swapModbusManagerToReal(); // thing1 { Configuration thingConfig = new Configuration(); @@ -95,6 +98,18 @@ public void testTwoDifferentEndpointWithDifferentParameters() { ModbusTcpThingHandler thingHandler = (ModbusTcpThingHandler) thing.getHandler(); assertNotNull(thingHandler); + + EndpointPoolConfiguration expectedPoolConfiguration = new EndpointPoolConfiguration(); + expectedPoolConfiguration.setConnectMaxTries(1); + expectedPoolConfiguration.setInterTransactionDelayMillis(1); + + // defaults + expectedPoolConfiguration.setConnectTimeoutMillis(10_000); + expectedPoolConfiguration.setInterConnectDelayMillis(0); + expectedPoolConfiguration.setReconnectAfterMillis(0); + + assertEquals(expectedPoolConfiguration, realModbusManager + .getEndpointPoolConfiguration(new ModbusTCPSlaveEndpoint("thisishost", 44, false))); } { Configuration thingConfig = new Configuration(); @@ -145,6 +160,22 @@ public void testTwoIdenticalEndpointWithDifferentParameters() { assertThat(thing.getStatus(), is(equalTo(ThingStatus.OFFLINE))); assertThat(thing.getStatusInfo().getStatusDetail(), is(equalTo(ThingStatusDetail.CONFIGURATION_ERROR))); } + { + // + // Ensure the right EndpointPoolConfiguration is still in place + // + EndpointPoolConfiguration expectedPoolConfiguration = new EndpointPoolConfiguration(); + expectedPoolConfiguration.setConnectMaxTries(1); + expectedPoolConfiguration.setInterTransactionDelayMillis(1); // Note: not 100 + + // defaults + expectedPoolConfiguration.setConnectTimeoutMillis(10_000); + expectedPoolConfiguration.setInterConnectDelayMillis(0); + expectedPoolConfiguration.setReconnectAfterMillis(0); + + assertEquals(expectedPoolConfiguration, realModbusManager + .getEndpointPoolConfiguration(new ModbusTCPSlaveEndpoint("thisishost", 44, false))); + } } @Test From cb9a11858149267b9a2c8dd7fc3a26b14b2e439a Mon Sep 17 00:00:00 2001 From: Seb Boulet Date: Wed, 17 Feb 2021 10:47:42 +0000 Subject: [PATCH 007/118] [gpio] Added instructions in case raspi-config is not available (#9899) Signed-off-by: Seb Boulet --- bundles/org.openhab.binding.gpio/README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.gpio/README.md b/bundles/org.openhab.binding.gpio/README.md index e935a735f753e..622cf483582bd 100644 --- a/bundles/org.openhab.binding.gpio/README.md +++ b/bundles/org.openhab.binding.gpio/README.md @@ -22,12 +22,25 @@ sudo raspi-config -> Interfacing Options --> Remote GPIO --> YES --> OK --> Finish +Note: if you are setting this up on a Raspberry Pi without `raspi-config` you can create the service config file manually: + +``` +sudo mkdir -p /etc/systemd/system/pigpiod.service.d/ +sudo nano /etc/systemd/system/pigpiod.service.d/public.conf +``` + [Service] + ExecStart= + ExecStart=/usr/bin/pigpiod +``` +sudo systemctl daemon-reload +``` +Now that Remote GPIO is enabled, get the daemon going: ``` sudo systemctl enable pigpiod sudo systemctl start pigpiod ``` -Set `host` to the address of the pi and the `port` to the port of pigpio (default: 8888). +In openHAB, set `host` to the address of the pi and the `port` to the port of pigpio (default: 8888). ## Channels From da5c4c8650429ad1e1513df4c82aa836083b7d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABl=20L=27hopital?= Date: Wed, 17 Feb 2021 11:50:14 +0100 Subject: [PATCH 008/118] [Synopanalyzer] Adding French localization, correction on overcast channel (#10113) * Adding French localization of the binding * Correction on overcast channel * Correcting nullable issues Signed-off-by: clinique --- .../SynopAnalyzerDiscoveryService.java | 11 ++--- .../internal/synop/Overcast.java | 2 +- .../internal/SynopAnalyzerHandlerFactory.java | 28 +++++------- .../handler/SynopAnalyzerHandler.java | 30 ++++++------- .../OH-INF/i18n/synopanalyzer_fr.properties | 44 +++++++++++++++++++ .../resources/OH-INF/thing/thing-types.xml | 12 +++-- 6 files changed, 85 insertions(+), 42 deletions(-) create mode 100644 bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer_fr.properties diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/discovery/SynopAnalyzerDiscoveryService.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/discovery/SynopAnalyzerDiscoveryService.java index a6ceab6b4fd11..860a579d0031d 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/discovery/SynopAnalyzerDiscoveryService.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/discovery/SynopAnalyzerDiscoveryService.java @@ -42,11 +42,12 @@ */ @NonNullByDefault public class SynopAnalyzerDiscoveryService extends AbstractDiscoveryService { - private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerDiscoveryService.class); private static final int DISCOVER_TIMEOUT_SECONDS = 5; - private LocationProvider locationProvider; - private final StationDB stationDB; + + private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerDiscoveryService.class); private final Map distances = new HashMap<>(); + private final LocationProvider locationProvider; + private final StationDB stationDB; /** * Creates a SynopAnalyzerDiscoveryService with enabled autostart. @@ -84,8 +85,8 @@ public void createResults(PointType serverLocation) { Integer nearestId = result.entrySet().iterator().next().getKey(); Optional station = stationDB.stations.stream().filter(s -> s.idOmm == nearestId).findFirst(); - thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, Integer.toString(nearestId))) - .withLabel("Synop : " + station.get().usualName) + thingDiscovered(DiscoveryResultBuilder.create(new ThingUID(THING_SYNOP, nearestId.toString())) + .withLabel(String.format("Synop : %s", station.get().usualName)) .withProperty(SynopAnalyzerConfiguration.STATION_ID, nearestId) .withRepresentationProperty(SynopAnalyzerConfiguration.STATION_ID).build()); } diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/synop/Overcast.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/synop/Overcast.java index ed80912891ec8..decfe3c2be0db 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/synop/Overcast.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyser/internal/synop/Overcast.java @@ -29,7 +29,7 @@ public enum Overcast { public static Overcast fromOcta(int octa) { if (octa == 0) { return Overcast.CLEAR_SKY; - } else if (octa > 0 && octa < 8) { + } else if (octa > 0 && octa < 9) { return Overcast.CLOUDY; } else if (octa == 9) { return Overcast.SKY_NOT_VISIBLE; diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerHandlerFactory.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerHandlerFactory.java index 3edec5c9c387c..aeaac3e11a348 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerHandlerFactory.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/SynopAnalyzerHandlerFactory.java @@ -18,9 +18,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.util.Collections; +import java.nio.charset.StandardCharsets; import java.util.Hashtable; -import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -55,28 +54,25 @@ @NonNullByDefault public class SynopAnalyzerHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandlerFactory.class); - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_SYNOP); private final LocationProvider locationProvider; - private final Gson gson; - private @NonNullByDefault({}) StationDB stationDB; + private final Gson gson = new Gson(); + private @Nullable StationDB stationDB; private @Nullable ServiceRegistration serviceReg; @Activate public SynopAnalyzerHandlerFactory(@Reference LocationProvider locationProvider) { this.locationProvider = locationProvider; - this.gson = new Gson(); } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + return THING_SYNOP.equals(thingTypeUID); } @Override protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - return thingTypeUID.equals(THING_SYNOP) ? new SynopAnalyzerHandler(thing, locationProvider, stationDB) : null; + return supportsThingType(thing.getThingTypeUID()) ? new SynopAnalyzerHandler(thing, locationProvider, stationDB) + : null; } @Override @@ -84,14 +80,14 @@ protected void activate(ComponentContext componentContext) { super.activate(componentContext); try (InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/db/stations.json"); - Reader reader = new InputStreamReader(is, "UTF-8");) { + Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);) { - stationDB = gson.fromJson(reader, StationDB.class); - registerDiscoveryService(); + StationDB stations = gson.fromJson(reader, StationDB.class); + registerDiscoveryService(stations); + this.stationDB = stations; logger.debug("Discovery service for Synop Stations registered."); } catch (IOException e) { logger.warn("Unable to read synop stations database"); - stationDB = new StationDB(); } } @@ -101,8 +97,8 @@ protected void deactivate(ComponentContext componentContext) { super.deactivate(componentContext); } - private void registerDiscoveryService() { - SynopAnalyzerDiscoveryService discoveryService = new SynopAnalyzerDiscoveryService(stationDB, locationProvider); + private void registerDiscoveryService(StationDB stations) { + SynopAnalyzerDiscoveryService discoveryService = new SynopAnalyzerDiscoveryService(stations, locationProvider); serviceReg = bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()); diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java index 06a7df4410595..28b7f5712f9a4 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/java/org/openhab/binding/synopanalyzer/internal/handler/SynopAnalyzerHandler.java @@ -28,12 +28,12 @@ import java.util.concurrent.TimeUnit; import javax.measure.quantity.Speed; +import javax.ws.rs.HttpMethod; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.synopanalyser.internal.synop.Overcast; import org.openhab.binding.synopanalyser.internal.synop.StationDB; -import org.openhab.binding.synopanalyser.internal.synop.StationDB.Station; import org.openhab.binding.synopanalyser.internal.synop.Synop; import org.openhab.binding.synopanalyser.internal.synop.SynopLand; import org.openhab.binding.synopanalyser.internal.synop.SynopMobile; @@ -77,12 +77,11 @@ public class SynopAnalyzerHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(SynopAnalyzerHandler.class); private @Nullable ScheduledFuture executionJob; - // private @NonNullByDefault({}) SynopAnalyzerConfiguration configuration; private @NonNullByDefault({}) String formattedStationId; private final LocationProvider locationProvider; - private final StationDB stationDB; + private final @Nullable StationDB stationDB; - public SynopAnalyzerHandler(Thing thing, LocationProvider locationProvider, StationDB stationDB) { + public SynopAnalyzerHandler(Thing thing, LocationProvider locationProvider, @Nullable StationDB stationDB) { super(thing); this.locationProvider = locationProvider; this.stationDB = stationDB; @@ -95,20 +94,18 @@ public void initialize() { logger.info("Scheduling Synop update thread to run every {} minute for Station '{}'", configuration.refreshInterval, formattedStationId); - if (thing.getProperties().isEmpty()) { - discoverAttributes(configuration.stationId); + StationDB stations = stationDB; + if (thing.getProperties().isEmpty() && stations != null) { + discoverAttributes(stations, configuration.stationId); } executionJob = scheduler.scheduleWithFixedDelay(this::updateSynopChannels, 0, configuration.refreshInterval, TimeUnit.MINUTES); - updateStatus(ThingStatus.UNKNOWN); } - protected void discoverAttributes(int stationId) { - final Map properties = new HashMap<>(); - - Optional station = stationDB.stations.stream().filter(s -> stationId == s.idOmm).findFirst(); - station.ifPresent(s -> { + protected void discoverAttributes(StationDB stations, int stationId) { + stations.stations.stream().filter(s -> stationId == s.idOmm).findFirst().ifPresent(s -> { + Map properties = new HashMap<>(); properties.put("Usual name", s.usualName); properties.put("Location", s.getLocation()); @@ -119,9 +116,8 @@ protected void discoverAttributes(int stationId) { properties.put("Distance", new QuantityType<>(distance, SIUnits.METRE).toString()); } + updateProperties(properties); }); - - updateProperties(properties); } private Optional getLastAvailableSynop() { @@ -129,7 +125,7 @@ private Optional getLastAvailableSynop() { String url = forgeURL(); try { - String answer = HttpUtil.executeUrl("GET", url, REQUEST_TIMEOUT_MS); + String answer = HttpUtil.executeUrl(HttpMethod.GET, url, REQUEST_TIMEOUT_MS); List messages = Arrays.asList(answer.split("\n")); if (!messages.isEmpty()) { String message = messages.get(messages.size() - 1); @@ -159,7 +155,9 @@ private void updateSynopChannels() { synop.ifPresent(theSynop -> { getThing().getChannels().forEach(channel -> { String channelId = channel.getUID().getId(); - updateState(channelId, getChannelState(channelId, theSynop)); + if (isLinked(channelId)) { + updateState(channelId, getChannelState(channelId, theSynop)); + } }); }); } diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer_fr.properties b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer_fr.properties new file mode 100644 index 0000000000000..70db3de5a2c1d --- /dev/null +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/i18n/synopanalyzer_fr.properties @@ -0,0 +1,44 @@ +# binding +binding.synopanalyzer.name = Extension Synop Analyzer +binding.synopanalyzer.description = Synop Analyzer permet de tlcharger et interprter les messages SYNOP. + +# thing type +thing-type.synopanalyzer.synopanalyzer.label = Message Synop +thing-type.synopanalyzer.synopanalyzer.description = Dcodage du dernier message d'une station Synop. + +# channel types +channel-type.synopanalyzer.wind-speed-beaufort.label = Beaufort +channel-type.synopanalyzer.wind-speed-beaufort.description = Force du vent sur l'chelle Beaufort. + +channel-type.synopanalyzer.wind-direction.label = Direction du vent +channel-type.synopanalyzer.wind-direction.description = Equivalent cardinal de la direction du vent. +# Only translating those that needs a french adaptation (containing "W") +channel-type.synopanalyzer.wind-direction.state.option.SSW = SSO +channel-type.synopanalyzer.wind-direction.state.option.SW = SO +channel-type.synopanalyzer.wind-direction.state.option.WSW = OSO +channel-type.synopanalyzer.wind-direction.state.option.W = O +channel-type.synopanalyzer.wind-direction.state.option.WNW = ONO +channel-type.synopanalyzer.wind-direction.state.option.NW = NO +channel-type.synopanalyzer.wind-direction.state.option.NNW = NNO + +channel-type.synopanalyzer.octa.label = Octa +channel-type.synopanalyzer.octa.description = Evaluation de la couverture nuageuse. + +channel-type.synopanalyzer.attenuation-factor.label = Coefficient d'attnuation +channel-type.synopanalyzer.attenuation-factor.description = Attnuation gnre par la couverture nuageuse. + +channel-type.synopanalyzer.overcast.label = Couverture nuageuse +channel-type.synopanalyzer.overcast.description = Apprciation de la couverture nuageuse. +channel-type.synopanalyzer.overcast.state.option.CLEAR_SKY = Ciel dgag +channel-type.synopanalyzer.overcast.state.option.CLOUDY = Nuageux +channel-type.synopanalyzer.overcast.state.option.SKY_NOT_VISIBLE = Ciel non visible + +channel-type.synopanalyzer.horizontal-visibility.label = Visibilit horizontale +channel-type.synopanalyzer.horizontal-visibility.description = Ordre de grandeur de la visibilit horizontale. +channel-type.synopanalyzer.horizontal-visibility.state.option.LESS_THAN_1 = Moins de 1 km +channel-type.synopanalyzer.horizontal-visibility.state.option.LESS_THAN_10 = Entre 1 et 10 km +channel-type.synopanalyzer.horizontal-visibility.state.option.LESS_THAN_50 = Entre 10 et 50 km +channel-type.synopanalyzer.horizontal-visibility.state.option.MORE_THAN_50 = Plus de 50 km + +channel-type.synopanalyzer.time-utc.label = Horodatage +channel-type.synopanalyzer.time-utc.description = Heure d'observation des mesures releves diff --git a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml index 39b8f3983eb09..3ee79409b7088 100644 --- a/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.synopanalyzer/src/main/resources/OH-INF/thing/thing-types.xml @@ -6,14 +6,14 @@ - The Synop Analyzer binding decodes Synop messages + This is the interpretation of the last message of a given station. - + @@ -75,14 +75,16 @@ Number - Octa + Cloud cover estimation. + sun_clouds - + Number Cloud layer mitigation factor + sun_clouds @@ -90,6 +92,7 @@ String Overcast + sun_clouds @@ -117,6 +120,7 @@ DateTime Timestamp when data was observed + time From 2b3e08de43c697ed5301b5fc1fb8e8740d9bce6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20T=C3=B3th?= Date: Wed, 17 Feb 2021 17:36:13 +0100 Subject: [PATCH 009/118] [pixometer] Update README.md, Typos corrected (#10168) Accepted without sign-off under small patch exemption. --- bundles/org.openhab.binding.pixometer/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.pixometer/README.md b/bundles/org.openhab.binding.pixometer/README.md index 88d0815c89165..89b6f1834936c 100644 --- a/bundles/org.openhab.binding.pixometer/README.md +++ b/bundles/org.openhab.binding.pixometer/README.md @@ -29,7 +29,7 @@ The different meter types are pretty similar in basic, but are implemented in pa | Parameter | Description | Required | |------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| resource_id | The ID which represents the current meter. You can find it in the pixometer browser app while editing a specific meter. It should look like this: "https://pixometer.io/portal/#/meters/XXXXX/edit" | Yes | +| resourceId | The ID which represents the current meter. You can find it in the pixometer browser app while editing a specific meter. It should look like this: "https://pixometer.io/portal/#/meters/XXXXX/edit" | Yes | ## Channels @@ -47,19 +47,19 @@ pixometer.things: ```java Bridge pixometer:account:AccountName "MyAccountName" [ user="xxxxxxxx@xxxx.xx", password="xxxxxxxxxxxx", refresh= 60 ] { - Thing energymeter MeterName1 "MyMeterName1" [ resource_id = "xxxxxxxx" ] - Thing gasmeter MeterName2 "MyMeterName2" [ resource_id = "xxxxxxxx" ] - Thing watermeter MeterName3 "MyMeterName3" [ resource_id = "xxxxxxxx" ] + Thing energymeter MeterName1 "MyMeterName1" [ resourceId = "xxxxxxxx" ] + Thing gasmeter MeterName2 "MyMeterName2" [ resourceId = "xxxxxxxx" ] + Thing watermeter MeterName3 "MyMeterName3" [ resourceId = "xxxxxxxx" ] } ``` pixometer.items: ```java -Number:Volume Meter_Gas_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:gasmeter:accountname:metername1:last_reading_value"} +Number:Volume Meter_Gas_ReadingValue "[%.3f %unit%]" [] {channel="pixometer:gasmeter:accountname:metername1:last_reading_value"} DateTime Meter_Gas_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:gasmeter:accountname:metername1:last_reading_date"} -Number:Energy Meter_Electricity_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:energymeter:accountname:metername2:last_reading_value"} +Number:Energy Meter_Electricity_ReadingValue "[%.1f %unit%]" [] {channel="pixometer:energymeter:accountname:metername2:last_reading_value"} DateTime Meter_Electricity_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:energymeter:accountname:metername2:last_reading_date"} -Number:Volume Meter_Water_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:watermeter:accountname:metername3:last_reading_value"} +Number:Volume Meter_Water_ReadingValue "[%.3f %unit%]" [] {channel="pixometer:watermeter:accountname:metername3:last_reading_value"} DateTime Meter_Water_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:watermeter:accountname:metername3:last_reading_date"} ``` From c0cec8028c713742f7d9e34ab1a6117a78e6de1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20D=C3=B6rfler?= Date: Wed, 17 Feb 2021 19:59:54 +0100 Subject: [PATCH 010/118] [pilight] Pilight Binding initial contribution (#9744) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Niklas Dörfler --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.pilight/NOTICE | 13 + bundles/org.openhab.binding.pilight/README.md | 119 ++++++++ bundles/org.openhab.binding.pilight/pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + .../pilight/internal/IPilightCallback.java | 64 +++++ .../internal/PilightBindingConstants.java | 45 +++ .../internal/PilightBridgeConfiguration.java | 53 ++++ .../internal/PilightChannelConfiguration.java | 34 +++ .../pilight/internal/PilightConnector.java | 264 ++++++++++++++++++ .../internal/PilightDeviceConfiguration.java | 35 +++ .../internal/PilightHandlerFactory.java | 89 ++++++ .../PilightBridgeDiscoveryService.java | 150 ++++++++++ .../PilightDeviceDiscoveryService.java | 223 +++++++++++++++ .../binding/pilight/internal/dto/Action.java | 66 +++++ .../pilight/internal/dto/AllStatus.java | 45 +++ .../binding/pilight/internal/dto/Code.java | 60 ++++ .../binding/pilight/internal/dto/Config.java | 40 +++ .../binding/pilight/internal/dto/Device.java | 140 ++++++++++ .../pilight/internal/dto/DeviceType.java | 33 +++ .../pilight/internal/dto/Identification.java | 52 ++++ .../binding/pilight/internal/dto/Message.java | 43 +++ .../binding/pilight/internal/dto/Options.java | 116 ++++++++ .../pilight/internal/dto/Response.java | 41 +++ .../binding/pilight/internal/dto/Status.java | 81 ++++++ .../binding/pilight/internal/dto/Values.java | 33 +++ .../binding/pilight/internal/dto/Version.java | 36 +++ .../internal/handler/PilightBaseHandler.java | 127 +++++++++ .../handler/PilightBridgeHandler.java | 233 ++++++++++++++++ .../handler/PilightContactHandler.java | 61 ++++ .../handler/PilightDimmerHandler.java | 126 +++++++++ .../handler/PilightGenericHandler.java | 138 +++++++++ .../handler/PilightSwitchHandler.java | 73 +++++ .../BooleanToIntegerSerializer.java | 41 +++ .../internal/types/PilightContactType.java | 31 ++ .../main/resources/OH-INF/binding/binding.xml | 11 + .../main/resources/OH-INF/config/config.xml | 15 + .../OH-INF/i18n/pilight_de.properties | 38 +++ .../main/resources/OH-INF/thing/bridge.xml | 39 +++ .../main/resources/OH-INF/thing/devices.xml | 96 +++++++ bundles/pom.xml | 1 + 42 files changed, 2937 insertions(+) create mode 100644 bundles/org.openhab.binding.pilight/NOTICE create mode 100644 bundles/org.openhab.binding.pilight/README.md create mode 100644 bundles/org.openhab.binding.pilight/pom.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml create mode 100644 bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml diff --git a/CODEOWNERS b/CODEOWNERS index e08a8885edf26..5a94b11062ae5 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -208,6 +208,7 @@ /bundles/org.openhab.binding.paradoxalarm/ @theater /bundles/org.openhab.binding.pentair/ @jsjames /bundles/org.openhab.binding.phc/ @gnlpfjh +/bundles/org.openhab.binding.pilight/ @stefanroellin @niklasdoerfler /bundles/org.openhab.binding.pioneeravr/ @Stratehm /bundles/org.openhab.binding.pixometer/ @Confectrician /bundles/org.openhab.binding.pjlinkdevice/ @nils diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 37bf387899738..c7dbcefbccc2c 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1026,6 +1026,11 @@ org.openhab.binding.phc ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.pilight + ${project.version} + org.openhab.addons.bundles org.openhab.binding.pioneeravr diff --git a/bundles/org.openhab.binding.pilight/NOTICE b/bundles/org.openhab.binding.pilight/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/NOTICE @@ -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 diff --git a/bundles/org.openhab.binding.pilight/README.md b/bundles/org.openhab.binding.pilight/README.md new file mode 100644 index 0000000000000..84d35ac52ac16 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/README.md @@ -0,0 +1,119 @@ +# pilight Binding + +The pilight binding allows openHAB to communicate with a [pilight](http://www.pilight.org/) instance running pilight +version 6.0 or greater. + +> pilight is a free open source full fledge domotica solution that runs on a Raspberry Pi, HummingBoard, BananaPi, +> Radxa, but also on *BSD and various linuxes (tested on Arch, Ubuntu and Debian). It's open source and freely available +> for anyone. pilight works with a great deal of devices and is frequency independent. Therefor, it can control devices +> working at 315Mhz, 433Mhz, 868Mhz etc. Support for these devices are dependent on community, because we as developers +> don't own them all. + +pilight is a cheap way to control 'Click On Click Off' devices. It started as an application for the Raspberry Pi (using +the GPIO interface) but it's also possible now to connect it to any other PC using an Arduino Nano. You will need a +cheap 433Mhz transceiver in both cases. See the [Pilight manual](https://manual.pilight.org/electronics/wiring.html) for +more information. + +## Supported Things + +| Thing | Type | Description | +|-----------|--------|----------------------------------------------------------------------------| +| `bridge` | Bridge | Pilight bridge required for the communication with the pilight daemon. | +| `contact` | Thing | Pilight contact (read-only). | +| `dimmer` | Thing | Pilight dimmer. | +| `switch` | Thing | Pilight switch. | +| `generic` | Thing | Pilight generic device for which you have to add the channels dynamically. | + +## Binding Configuration + +### `bridge` Thing + +A `bridge` is required for the communication with a pilight daemon. Multiple pilight instances are supported by creating +different pilight `bridge` things. + +The `bridge` requires the following configuration parameters: + +| Parameter Label | Parameter ID | Description | Required | +|-----------------|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| IP Address | ipAddress | Host name or IP address of the pilight daemon | yes | +| Port | port | Port number on which the pilight daemon is listening. Default: 5000 | yes | +| Delay | delay | Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000. Recommended value with band pass filter: somewhere between 200-500. Default: 500 | no | + +Important: you must explicitly configure the port in the pilight daemon config or otherwise a random port will be used +and the binding will not be able to connect. + +### `contact`, `dimmer`, `switch`, `generic` Things + +These things have all one required parameter: + +| Parameter Label | Parameter ID | Description | Required | +|-----------------|--------------|------------------------|----------| +| Name | name | Name of pilight device | yes | + +## Channels + +The `bridge` thing has no channels. + +The `contact`, `dimmer` and `switch` things all have one channel: + +| Thing | Channel | Type | Description | +|-----------|----------|---------|-------------------------| +| `contact` | state | Contact | State of the contact | +| `dimmer` | dimlevel | Dimmer | Dim level of the dimmer | +| `switch` | state | Switch | State of the switch | + +The `generic` thing has no fixed channels, so you have to add them manually. Currently, only String and Number channels +are supported. + +## Auto Discovery + +### Bridge Auto Discovery + +The pilight daemon implements a SSDP interface, which can be used to search for running pilight daemon instances by +sending a SSDP request via multicast udp (this mechanism may only work if +the [standalone mode](https://manual.pilight.org/configuration/settings.html#standalone) in the pilight daemon is +disabled. After loading the binding this bridge discovery is automatically run and scheduled to scan for bridges every +10 minutes. + +### Device Auto Discovery + +After a `bridge` thing has been configured in openHAB, it automatically establishes a connection between pilight daemon +and openHAB. As soon as the bridge is connected, the devices configured in the pilight daemon are automatically found +via autodiscovery in background (or via a manually triggered discovery) and are displayed in the inbox to easily create +things from them. + +## Full Example + +things/pilight.things + +``` +Bridge pilight:bridge:raspi "Pilight Daemon raspi" [ ipAddress="192.168.1.1", port=5000 ] { + Thing switch office "Office" [ name="office" ] + Thing dimmer piano "Piano" [ name="piano" ] + Thing generic weather "Weather" [ name="weather" ] { + Channels: + State Number : temperature [ property="temperature"] + State Number : humidity [ property="humidity"] + } +} +``` + +items/pilight.items + +``` +Switch office_switch "Büro" { channel="pilight:switch:raspi:office:state" } +Dimmer piano_light "Klavier [%.0f %%]" { channel="pilight:dimmer:raspi:piano:dimlevel" } +Number weather_temperature "Aussentemperatur [%.1f °C]" { channel="pilight:generic:raspi:weather:temperature" } +Number weather_humidity "Feuchtigkeit [%.0f %%]" { channel="pilight:generic:raspi:weather:humidity" } + +``` + +sitemaps/fragment.sitemap + +``` +Switch item=office_switch +Slider item=piano_light +Text item=weather_temperature +Text item=weather_humidity +``` + diff --git a/bundles/org.openhab.binding.pilight/pom.xml b/bundles/org.openhab.binding.pilight/pom.xml new file mode 100644 index 0000000000000..1acde190f7394 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.pilight + + openHAB Add-ons :: Bundles :: Pilight Binding + + diff --git a/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml new file mode 100644 index 0000000000000..38a968d3f2ba8 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.pilight/${project.version} + + diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java new file mode 100644 index 0000000000000..9caa84f3f976f --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/IPilightCallback.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.dto.Version; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; + +/** + * Callback interface to signal any listeners that an update was received from pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public interface IPilightCallback { + + /** + * Update thing status + * + * @param status status of thing + * @param statusDetail status detail of thing + * @param description description of thing status + */ + void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description); + + /** + * Update for one or more device received. + * + * @param allStatus list of Object containing list of devices that were updated and their current state + */ + void statusReceived(List allStatus); + + /** + * Configuration received. + * + * @param config Object containing configuration of pilight + */ + void configReceived(Config config); + + /** + * Version information received. + * + * @param version Object containing software version information of pilight daemon + */ + void versionReceived(Version version); +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java new file mode 100644 index 0000000000000..bcf731315b83c --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBindingConstants.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link PilightBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBindingConstants { + + public static final String BINDING_ID = "pilight"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + public static final ThingTypeUID THING_TYPE_CONTACT = new ThingTypeUID(BINDING_ID, "contact"); + public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer"); + public static final ThingTypeUID THING_TYPE_SWITCH = new ThingTypeUID(BINDING_ID, "switch"); + public static final ThingTypeUID THING_TYPE_GENERIC = new ThingTypeUID(BINDING_ID, "generic"); + + // List of property names + public static final String PROPERTY_IP_ADDRESS = "ipAddress"; + public static final String PROPERTY_PORT = "port"; + public static final String PROPERTY_NAME = "name"; + + // List of all Channel ids + public static final String CHANNEL_STATE = "state"; + public static final String CHANNEL_DIMLEVEL = "dimlevel"; +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java new file mode 100644 index 0000000000000..85dce4004946f --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightBridgeConfiguration.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightBridgeConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBridgeConfiguration { + + private String ipAddress = ""; + private int port = 0; + private int delay = 500; + + public String getIpAddress() { + return ipAddress; + } + + public void setIpAddress(String ipAddress) { + this.ipAddress = ipAddress; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public int getDelay() { + return delay; + } + + public void setDelay(Integer delay) { + this.delay = delay; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java new file mode 100644 index 0000000000000..594eff9d77088 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightChannelConfiguration.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightChannelConfiguration} class contains fields mapping channel configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightChannelConfiguration { + private String property = ""; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java new file mode 100644 index 0000000000000..75fca4f9cf4e2 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightConnector.java @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import java.io.*; +import java.net.Socket; +import java.util.Collections; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.*; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.MappingJsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * This class listens for updates from the pilight daemon. It is also responsible for requesting + * and propagating the current pilight configuration. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + * + */ +@NonNullByDefault +public class PilightConnector implements Runnable, Closeable { + + private static final int RECONNECT_DELAY_MSEC = 10 * 1000; // 10 seconds + + private final Logger logger = LoggerFactory.getLogger(PilightConnector.class); + + private final PilightBridgeConfiguration config; + + private final IPilightCallback callback; + + private final ObjectMapper inputMapper = new ObjectMapper( + new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false)); + + private final ObjectMapper outputMapper = new ObjectMapper( + new MappingJsonFactory().configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, false)) + .setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL); + + private @Nullable Socket socket; + private @Nullable PrintStream printStream; + + private final ScheduledExecutorService scheduler; + private final ConcurrentLinkedQueue delayedActionQueue = new ConcurrentLinkedQueue<>(); + private @Nullable ScheduledFuture delayedActionWorkerFuture; + + public PilightConnector(final PilightBridgeConfiguration config, final IPilightCallback callback, + final ScheduledExecutorService scheduler) { + this.config = config; + this.callback = callback; + this.scheduler = scheduler; + } + + @Override + public void run() { + try { + connect(); + + while (!Thread.currentThread().isInterrupted()) { + try { + final @Nullable Socket socket = this.socket; + if (socket != null && !socket.isClosed()) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) { + String line = in.readLine(); + while (!Thread.currentThread().isInterrupted() && line != null) { + if (!line.isEmpty()) { + logger.trace("Received from pilight: {}", line); + final ObjectMapper inputMapper = this.inputMapper; + if (line.startsWith("{\"message\":\"config\"")) { + final @Nullable Message message = inputMapper.readValue(line, Message.class); + callback.configReceived(message.getConfig()); + } else if (line.startsWith("{\"message\":\"values\"")) { + final @Nullable AllStatus status = inputMapper.readValue(line, AllStatus.class); + callback.statusReceived(status.getValues()); + } else if (line.startsWith("{\"version\":")) { + final @Nullable Version version = inputMapper.readValue(line, Version.class); + callback.versionReceived(version); + } else if (line.startsWith("{\"status\":")) { + // currently unused + } else if (line.equals("1")) { + throw new IOException("Connection to pilight lost"); + } else { + final @Nullable Status status = inputMapper.readValue(line, Status.class); + callback.statusReceived(Collections.singletonList(status)); + } + } + + line = in.readLine(); + } + } + } + } catch (IOException e) { + if (!Thread.currentThread().isInterrupted()) { + logger.debug("Error in pilight listener thread: {}", e.getMessage()); + } + } + + logger.debug("Disconnected from pilight server at {}:{}", config.getIpAddress(), config.getPort()); + + if (!Thread.currentThread().isInterrupted()) { + callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, null); + // empty line received (socket closed) or pilight stopped but binding + // is still running, try to reconnect + connect(); + } + } + + } catch (InterruptedException e) { + logger.debug("Interrupting thread."); + Thread.currentThread().interrupt(); + } + } + + /** + * Tells the connector to refresh the configuration + */ + public void refreshConfig() { + doSendAction(new Action(Action.ACTION_REQUEST_CONFIG)); + } + + /** + * Tells the connector to refresh the status of all devices + */ + public void refreshStatus() { + doSendAction(new Action(Action.ACTION_REQUEST_VALUES)); + } + + /** + * Stops the listener + */ + public void close() { + disconnect(); + Thread.currentThread().interrupt(); + } + + private void disconnect() { + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + printStream.close(); + this.printStream = null; + } + + final @Nullable Socket socket = this.socket; + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + logger.debug("Error while closing pilight socket: {}", e.getMessage()); + } + this.socket = null; + } + } + + private boolean isConnected() { + final @Nullable Socket socket = this.socket; + return socket != null && !socket.isClosed(); + } + + private void connect() throws InterruptedException { + disconnect(); + + int delay = 0; + + while (!isConnected()) { + try { + logger.debug("pilight connecting to {}:{}", config.getIpAddress(), config.getPort()); + + Thread.sleep(delay); + Socket socket = new Socket(config.getIpAddress(), config.getPort()); + + Options options = new Options(); + options.setConfig(true); + + Identification identification = new Identification(); + identification.setOptions(options); + + // For some reason, directly using the outputMapper to write to the socket's OutputStream doesn't work. + PrintStream printStream = new PrintStream(socket.getOutputStream(), true); + printStream.println(outputMapper.writeValueAsString(identification)); + + final @Nullable Response response = inputMapper.readValue(socket.getInputStream(), Response.class); + + if (response.getStatus().equals(Response.SUCCESS)) { + logger.debug("Established connection to pilight server at {}:{}", config.getIpAddress(), + config.getPort()); + this.socket = socket; + this.printStream = printStream; + callback.updateThingStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, null); + } else { + printStream.close(); + socket.close(); + logger.debug("pilight client not accepted: {}", response.getStatus()); + } + } catch (IOException e) { + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + printStream.close(); + } + logger.debug("connect failed: {}", e.getMessage()); + callback.updateThingStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + + delay = RECONNECT_DELAY_MSEC; + } + } + + /** + * send action to pilight daemon + * + * @param action action to send + */ + public void sendAction(Action action) { + delayedActionQueue.add(action); + final @Nullable ScheduledFuture delayedActionWorkerFuture = this.delayedActionWorkerFuture; + + if (delayedActionWorkerFuture == null || delayedActionWorkerFuture.isCancelled()) { + this.delayedActionWorkerFuture = scheduler.scheduleWithFixedDelay(() -> { + if (!delayedActionQueue.isEmpty()) { + doSendAction(delayedActionQueue.poll()); + } else { + final @Nullable ScheduledFuture workerFuture = this.delayedActionWorkerFuture; + if (workerFuture != null) { + workerFuture.cancel(false); + } + this.delayedActionWorkerFuture = null; + } + }, 0, config.getDelay(), TimeUnit.MILLISECONDS); + } + } + + private void doSendAction(Action action) { + final @Nullable PrintStream printStream = this.printStream; + if (printStream != null) { + try { + printStream.println(outputMapper.writeValueAsString(action)); + } catch (IOException e) { + logger.debug("Error while sending action '{}' to pilight server: {}", action.getAction(), + e.getMessage()); + } + } else { + logger.debug("Cannot send action '{}', not connected to pilight!", action.getAction()); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java new file mode 100644 index 0000000000000..b5cbff1f000ab --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightDeviceConfiguration.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PilightDeviceConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightDeviceConfiguration { + + private String name = ""; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java new file mode 100644 index 0000000000000..366b2e4b21de8 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/PilightHandlerFactory.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.*; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.handler.PilightBridgeHandler; +import org.openhab.binding.pilight.internal.handler.PilightContactHandler; +import org.openhab.binding.pilight.internal.handler.PilightDimmerHandler; +import org.openhab.binding.pilight.internal.handler.PilightGenericHandler; +import org.openhab.binding.pilight.internal.handler.PilightSwitchHandler; +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.openhab.core.thing.type.ChannelTypeRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link PilightHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +@Component(configurationPid = "binding.pilight", service = ThingHandlerFactory.class) +public class PilightHandlerFactory extends BaseThingHandlerFactory { + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_BRIDGE, THING_TYPE_CONTACT, + THING_TYPE_DIMMER, THING_TYPE_GENERIC, THING_TYPE_SWITCH); + + private final ChannelTypeRegistry channelTypeRegistry; + + @Activate + public PilightHandlerFactory(@Reference ChannelTypeRegistry channelTypeRegistry) { + this.channelTypeRegistry = channelTypeRegistry; + } + + @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_BRIDGE.equals(thingTypeUID)) { + return new PilightBridgeHandler((Bridge) thing); + } + + if (THING_TYPE_CONTACT.equals(thingTypeUID)) { + return new PilightContactHandler(thing); + } + + if (THING_TYPE_DIMMER.equals(thingTypeUID)) { + return new PilightDimmerHandler(thing); + } + + if (THING_TYPE_GENERIC.equals(thingTypeUID)) { + return new PilightGenericHandler(thing, channelTypeRegistry); + } + + if (THING_TYPE_SWITCH.equals(thingTypeUID)) { + return new PilightSwitchHandler(thing); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java new file mode 100644 index 0000000000000..67cb46bd87b6e --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightBridgeDiscoveryService.java @@ -0,0 +1,150 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.discovery; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightBindingConstants; +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.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightBridgeDiscoveryService} is responsible for discovering new pilight daemons on the network + * by sending a ssdp multicast request via udp. + * + * @author Niklas Dörfler - Initial contribution + */ +@NonNullByDefault +@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.pilight") +public class PilightBridgeDiscoveryService extends AbstractDiscoveryService { + + private static final int AUTODISCOVERY_SEARCH_TIME_SEC = 5; + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10; + + private static final String SSDP_DISCOVERY_REQUEST_MESSAGE = "M-SEARCH * HTTP/1.1\r\n" + + "Host:239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:service:pilight:1\r\n" + + "Man:\"ssdp:discover\"\r\n" + "MX:3\r\n\r\n"; + public static final String SSDP_MULTICAST_ADDRESS = "239.255.255.250"; + public static final int SSDP_PORT = 1900; + public static final int SSDP_WAIT_TIMEOUT = 2000; // in milliseconds + + private final Logger logger = LoggerFactory.getLogger(PilightBridgeDiscoveryService.class); + + private @Nullable ScheduledFuture backgroundDiscoveryJob; + + public PilightBridgeDiscoveryService() throws IllegalArgumentException { + super(getSupportedThingTypeUIDs(), AUTODISCOVERY_SEARCH_TIME_SEC, true); + } + + public static Set getSupportedThingTypeUIDs() { + return Collections.singleton(PilightBindingConstants.THING_TYPE_BRIDGE); + } + + @Override + protected void startScan() { + logger.debug("Pilight bridge discovery scan started"); + removeOlderResults(getTimestampOfLastScan()); + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface nic : interfaces) { + Enumeration inetAddresses = nic.getInetAddresses(); + for (InetAddress inetAddress : Collections.list(inetAddresses)) { + if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { + DatagramSocket ssdp = new DatagramSocket( + new InetSocketAddress(inetAddress.getHostAddress(), 0)); + byte[] buff = SSDP_DISCOVERY_REQUEST_MESSAGE.getBytes(StandardCharsets.UTF_8); + DatagramPacket sendPack = new DatagramPacket(buff, buff.length); + sendPack.setAddress(InetAddress.getByName(SSDP_MULTICAST_ADDRESS)); + sendPack.setPort(SSDP_PORT); + ssdp.send(sendPack); + ssdp.setSoTimeout(SSDP_WAIT_TIMEOUT); + + boolean loop = true; + while (loop) { + DatagramPacket recvPack = new DatagramPacket(new byte[1024], 1024); + ssdp.receive(recvPack); + byte[] recvData = recvPack.getData(); + + final Scanner scanner = new Scanner(new ByteArrayInputStream(recvData), + StandardCharsets.UTF_8); + loop = scanner.findAll("Location:([0-9.]+):(.*)").peek(matchResult -> { + final String server = matchResult.group(1); + final Integer port = Integer.parseInt(matchResult.group(2)); + final String bridgeName = server.replace(".", "") + "" + port; + + logger.debug("Found pilight daemon at {}:{}", server, port); + + Map properties = new HashMap<>(); + properties.put(PilightBindingConstants.PROPERTY_IP_ADDRESS, server); + properties.put(PilightBindingConstants.PROPERTY_PORT, port); + properties.put(PilightBindingConstants.PROPERTY_NAME, bridgeName); + + ThingUID uid = new ThingUID(PilightBindingConstants.THING_TYPE_BRIDGE, bridgeName); + + DiscoveryResult result = DiscoveryResultBuilder.create(uid).withProperties(properties) + .withRepresentationProperty(PilightBindingConstants.PROPERTY_NAME) + .withLabel("Pilight Bridge (" + server + ")").build(); + + thingDiscovered(result); + }).count() == 0; + } + } + } + } + } catch (IOException e) { + if (e.getMessage() != null && !"Receive timed out".equals(e.getMessage())) { + logger.warn("Unable to enumerate the local network interfaces {}", e.getMessage()); + } + } + } + + @Override + protected synchronized void stopScan() { + super.stopScan(); + removeOlderResults(getTimestampOfLastScan()); + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 5, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + this.backgroundDiscoveryJob = null; + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java new file mode 100644 index 0000000000000..c735a976e36a5 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/discovery/PilightDeviceDiscoveryService.java @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.discovery; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.*; + +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightHandlerFactory; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.DeviceType; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.handler.PilightBridgeHandler; +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.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightDeviceDiscoveryService} discovers pilight devices after a bridge thing has been created and + * connected to the pilight daemon. Things are discovered periodically in the background or after a manual trigger. + * + * @author Niklas Dörfler - Initial contribution + */ +@NonNullByDefault +public class PilightDeviceDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService { + + private static final Set SUPPORTED_THING_TYPES_UIDS = PilightHandlerFactory.SUPPORTED_THING_TYPES_UIDS; + + private static final int AUTODISCOVERY_SEARCH_TIME_SEC = 10; + private static final int AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC = 60 * 10; + + private final Logger logger = LoggerFactory.getLogger(PilightDeviceDiscoveryService.class); + + private @Nullable PilightBridgeHandler pilightBridgeHandler; + private @Nullable ThingUID bridgeUID; + + private @Nullable ScheduledFuture backgroundDiscoveryJob; + private CompletableFuture configFuture; + private CompletableFuture> statusFuture; + + public PilightDeviceDiscoveryService() { + super(SUPPORTED_THING_TYPES_UIDS, AUTODISCOVERY_SEARCH_TIME_SEC); + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + } + + @Override + protected void startScan() { + if (pilightBridgeHandler != null) { + configFuture = new CompletableFuture<>(); + statusFuture = new CompletableFuture<>(); + + configFuture.thenAcceptBoth(statusFuture, (config, allStatus) -> { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + config.getDevices().forEach((deviceId, device) -> { + if (this.pilightBridgeHandler != null) { + final Optional status = allStatus.stream() + .filter(s -> s.getDevices().contains(deviceId)).findFirst(); + + final ThingTypeUID thingTypeUID; + final String typeString; + + if (status.isPresent()) { + if (status.get().getType().equals(DeviceType.SWITCH)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_SWITCH.getId()); + typeString = "Switch"; + } else if (status.get().getType().equals(DeviceType.DIMMER)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_DIMMER.getId()); + typeString = "Dimmer"; + } else if (status.get().getType().equals(DeviceType.VALUE)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } else if (status.get().getType().equals(DeviceType.CONTACT)) { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_CONTACT.getId()); + typeString = "Contact"; + } else { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } + } else { + thingTypeUID = new ThingTypeUID(BINDING_ID, THING_TYPE_GENERIC.getId()); + typeString = "Generic"; + } + + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + final ThingUID thingUID = new ThingUID(thingTypeUID, + pilightBridgeHandler.getThing().getUID(), deviceId); + + final Map properties = new HashMap<>(); + properties.put(PROPERTY_NAME, deviceId); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID) + .withThingType(thingTypeUID).withProperties(properties).withBridge(bridgeUID) + .withRepresentationProperty(PROPERTY_NAME) + .withLabel("Pilight " + typeString + " Device '" + deviceId + "'").build(); + + thingDiscovered(discoveryResult); + } + } + }); + }); + + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + pilightBridgeHandler.refreshConfigAndStatus(); + } + } + } + + @Override + protected synchronized void stopScan() { + super.stopScan(); + configFuture.cancel(true); + statusFuture.cancel(true); + if (bridgeUID != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + } + } + + @Override + protected void startBackgroundDiscovery() { + logger.debug("Start Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob == null || backgroundDiscoveryJob.isCancelled()) { + this.backgroundDiscoveryJob = scheduler.scheduleWithFixedDelay(this::startScan, 20, + AUTODISCOVERY_BACKGROUND_SEARCH_INTERVAL_SEC, TimeUnit.SECONDS); + } + } + + @Override + protected void stopBackgroundDiscovery() { + logger.debug("Stop Pilight device background discovery"); + final @Nullable ScheduledFuture backgroundDiscoveryJob = this.backgroundDiscoveryJob; + if (backgroundDiscoveryJob != null) { + backgroundDiscoveryJob.cancel(true); + this.backgroundDiscoveryJob = null; + } + } + + @Override + public void setThingHandler(final ThingHandler handler) { + if (handler instanceof PilightBridgeHandler) { + this.pilightBridgeHandler = (PilightBridgeHandler) handler; + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + bridgeUID = pilightBridgeHandler.getThing().getUID(); + } + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return pilightBridgeHandler; + } + + @Override + public void activate() { + super.activate(null); + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + pilightBridgeHandler.registerDiscoveryListener(this); + } + } + + @Override + public void deactivate() { + if (bridgeUID != null) { + removeOlderResults(getTimestampOfLastScan(), bridgeUID); + } + + final @Nullable PilightBridgeHandler pilightBridgeHandler = this.pilightBridgeHandler; + if (pilightBridgeHandler != null) { + pilightBridgeHandler.unregisterDiscoveryListener(); + } + + super.deactivate(); + } + + /** + * Method used to get pilight device config into the discovery class. + * + * @param config config to get + */ + public void setConfig(Config config) { + configFuture.complete(config); + } + + /** + * Method used to get pilight device status list into the discovery class. + * + * @param status list of status objects + */ + public void setStatus(List status) { + statusFuture.complete(status); + } + + @Override + public Set getSupportedThingTypes() { + return SUPPORTED_THING_TYPES_UIDS; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java new file mode 100644 index 0000000000000..ed0a9259295db --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Action.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * This message is sent when we want to change the state of a device or request the + * current configuration in pilight. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Action { + + public static final String ACTION_SEND = "send"; + + public static final String ACTION_CONTROL = "control"; + + public static final String ACTION_REQUEST_CONFIG = "request config"; + + public static final String ACTION_REQUEST_VALUES = "request values"; + + private String action; + + private Code code; + + private Options options; + + public Action(String action) { + this.action = action; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Code getCode() { + return code; + } + + public void setCode(Code code) { + this.code = code; + } + + public Options getOptions() { + return options; + } + + public void setOptions(Options options) { + this.options = options; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java new file mode 100644 index 0000000000000..69418aa581ea0 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/AllStatus.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import java.util.ArrayList; +import java.util.List; + +/** + * All status messages. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class AllStatus { + + private String message; + + private List values = new ArrayList<>(); + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java new file mode 100644 index 0000000000000..9cc253bc1407e --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Code.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * Part of the {@link Action} message that is sent to pilight. + * This contains the desired state for a single device. + * + * {@link http://www.pilight.org/development/api/#sender} + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Code { + + public static final String STATE_ON = "on"; + + public static final String STATE_OFF = "off"; + + private String device; + + private String state; + + private Values values; + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Values getValues() { + return values; + } + + public void setValues(Values values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java new file mode 100644 index 0000000000000..6f368a62f1d4d --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Config.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * pilight configuration object + * + * {@link http://www.pilight.org/development/api/#controller} + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Config { + + private Map devices; + + public Map getDevices() { + return devices; + } + + public void setDevices(Map devices) { + this.devices = devices; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java new file mode 100644 index 0000000000000..62c6c26cdcdb4 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Device.java @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Class describing a device in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Device { + + private String uuid; + + private String origin; + + private String timestamp; + + private List protocol; + + private String state; + + private Integer dimlevel = null; + + // @SerializedName("dimlevel-maximum") + private Integer dimlevelMaximum = null; + + private Integer dimlevelMinimum = null; + + private List> id; + + private Map properties = new HashMap<>(); + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public List getProtocol() { + return protocol; + } + + public void setProtocol(List protocol) { + this.protocol = protocol; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Integer getDimlevel() { + return dimlevel; + } + + public void setDimlevel(Integer dimlevel) { + this.dimlevel = dimlevel; + } + + public Integer getDimlevelMaximum() { + return dimlevelMaximum; + } + + @JsonProperty("dimlevel-maximum") + public void setDimlevelMaximum(Integer dimlevelMaximum) { + this.dimlevelMaximum = dimlevelMaximum; + } + + public Integer getDimlevelMinimum() { + return dimlevelMinimum; + } + + @JsonProperty("dimlevel-minimum") + public void setDimlevelMinimum(Integer dimlevelMinimum) { + this.dimlevelMinimum = dimlevelMinimum; + } + + public List> getId() { + return id; + } + + public void setId(List> id) { + this.id = id; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public Map getProperties() { + return properties; + } + + @JsonAnySetter + public void set(String name, Object value) { + properties.put(name, value.toString()); + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java new file mode 100644 index 0000000000000..19a1929287d7d --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/DeviceType.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * Different types of devices in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class DeviceType { + + public static final Integer SERVER = -1; + + public static final Integer SWITCH = 1; + + public static final Integer DIMMER = 2; + + public static final Integer VALUE = 3; + + public static final Integer CONTACT = 6; +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java new file mode 100644 index 0000000000000..749492a5d3be6 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Identification.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * This object is sent to pilight right after the initial connection. It describes what kind of client we want to be. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class Identification { + + public static final String ACTION_IDENTIFY = "identify"; + + private String action; + + private Options options = new Options(); + + public Identification() { + this.action = ACTION_IDENTIFY; + } + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public Options getOptions() { + return options; + } + + public void setOptions(Options options) { + this.options = options; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java new file mode 100644 index 0000000000000..449f204ca47cf --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Message.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * Wrapper for the {@code Config} object + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Message { + + private Config config; + + private String message; + + public Config getConfig() { + return config; + } + + public void setConfig(Config config) { + this.config = config; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java new file mode 100644 index 0000000000000..3f639c819969b --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Options.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import org.openhab.binding.pilight.internal.serializers.BooleanToIntegerSerializer; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +/** + * Options that can be set as a pilight client. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Options { + + public static final String MEDIA_ALL = "all"; + + public static final String MEDIA_WEB = "web"; + + public static final String MEDIA_MOBILE = "mobile"; + + public static final String MEDIA_DESKTOP = "desktop"; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean core; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean receiver; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean config; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean forward; + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonSerialize(using = BooleanToIntegerSerializer.class) + private Boolean stats; + + private String uuid; + + private String media; + + public Boolean getCore() { + return core; + } + + public void setCore(Boolean core) { + this.core = core; + } + + public Boolean getReceiver() { + return receiver; + } + + public void setReceiver(Boolean receiver) { + this.receiver = receiver; + } + + public Boolean getConfig() { + return config; + } + + public void setConfig(Boolean config) { + this.config = config; + } + + public Boolean getForward() { + return forward; + } + + public void setForward(Boolean forward) { + this.forward = forward; + } + + public Boolean getStats() { + return stats; + } + + public void setStats(Boolean stats) { + this.stats = stats; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getMedia() { + return media; + } + + public void setMedia(String media) { + this.media = media; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java new file mode 100644 index 0000000000000..bdde312ae96f3 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Response.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * Response to a connection or state change request + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Response { + + public static final String SUCCESS = "success"; + + public static final String FAILURE = "failure"; + + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public boolean isSuccess() { + return SUCCESS.equals(status); + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java new file mode 100644 index 0000000000000..8c5c4a3afd055 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Status.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A Status message is received when a device in pilight changes state. + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Status { + + private String origin; + + private Integer type; + + private String uuid; + + private List devices = new ArrayList<>(); + + private Map values = new HashMap<>(); + + public Status() { + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java new file mode 100644 index 0000000000000..65798b7eacb22 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Values.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +/** + * Describes the specific properties of a device + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public class Values { + + private Integer dimlevel; + + public Integer getDimlevel() { + return dimlevel; + } + + public void setDimlevel(Integer dimlevel) { + this.dimlevel = dimlevel; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java new file mode 100644 index 0000000000000..755eff8c4da79 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/dto/Version.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * pilight version information object + * + * {@link http://www.pilight.org/development/api/#controller} + * + * @author Niklas Dörfler - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Version { + + private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java new file mode 100644 index 0000000000000..aeb1e4845a1fe --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBaseHandler.java @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightDeviceConfiguration; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Config; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.BridgeHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightBaseHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public abstract class PilightBaseHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightBaseHandler.class); + + private String name = ""; + + public PilightBaseHandler(Thing thing) { + super(thing); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + refreshConfigAndStatus(); + return; + } + + @Nullable + Action action = createUpdateCommand(channelUID, command); + if (action != null) { + sendAction(action); + } + } + + @Override + public void initialize() { + PilightDeviceConfiguration config = getConfigAs(PilightDeviceConfiguration.class); + name = config.getName(); + + refreshConfigAndStatus(); + } + + public void updateFromStatusIfMatches(Status status) { + if (status.getDevices() != null && !status.getDevices().isEmpty()) { + if (name.equals(status.getDevices().get(0))) { + if (!ThingStatus.ONLINE.equals(getThing().getStatus())) { + updateStatus(ThingStatus.ONLINE); + } + updateFromStatus(status); + } + } + } + + public void updateFromConfigIfMatches(Config config) { + Device device = config.getDevices().get(getName()); + if (device != null) { + updateFromConfigDevice(device); + } + } + + abstract void updateFromStatus(Status status); + + abstract void updateFromConfigDevice(Device device); + + abstract @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command); + + protected String getName() { + return name; + } + + private void sendAction(Action action) { + final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler(); + if (handler != null) { + handler.sendAction(action); + } else { + logger.warn("No pilight bridge handler found to send action."); + } + } + + private void refreshConfigAndStatus() { + final @Nullable PilightBridgeHandler handler = getPilightBridgeHandler(); + if (handler != null) { + handler.refreshConfigAndStatus(); + } else { + logger.warn("No pilight bridge handler found to refresh config and status."); + } + } + + private @Nullable PilightBridgeHandler getPilightBridgeHandler() { + final @Nullable Bridge bridge = getBridge(); + if (bridge != null) { + @Nullable + BridgeHandler handler = bridge.getHandler(); + if (handler instanceof PilightBridgeHandler) { + return (PilightBridgeHandler) handler; + } + } + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java new file mode 100644 index 0000000000000..dfcfd9fdfd087 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightBridgeHandler.java @@ -0,0 +1,233 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.IPilightCallback; +import org.openhab.binding.pilight.internal.PilightBridgeConfiguration; +import org.openhab.binding.pilight.internal.PilightConnector; +import org.openhab.binding.pilight.internal.discovery.PilightDeviceDiscoveryService; +import org.openhab.binding.pilight.internal.dto.*; +import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightBridgeHandler} is responsible dispatching commands for the child + * things to the Pilight daemon and sending status updates to the child things. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightBridgeHandler extends BaseBridgeHandler { + + private static final int REFRESH_CONFIG_MSEC = 500; + + private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class); + + private @Nullable PilightConnector connector = null; + + private @Nullable ScheduledFuture refreshJob = null; + + private @Nullable PilightDeviceDiscoveryService discoveryService = null; + + private final ExecutorService connectorExecutor = Executors + .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true)); + + public PilightBridgeHandler(Bridge bridge) { + super(bridge); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.debug("Pilight Bridge is read-only and does not handle commands."); + } + + @Override + public void initialize() { + PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class); + + final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService; + PilightConnector connector = new PilightConnector(config, new IPilightCallback() { + @Override + public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail, + @Nullable String description) { + updateStatus(status, statusDetail, description); + if (status == ThingStatus.ONLINE) { + refreshConfigAndStatus(); + } + } + + @Override + public void statusReceived(List allStatus) { + for (Status status : allStatus) { + processStatus(status); + } + + if (discoveryService != null) { + discoveryService.setStatus(allStatus); + } + } + + @Override + public void configReceived(Config config) { + processConfig(config); + } + + @Override + public void versionReceived(Version version) { + getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, version.getVersion()); + } + }, scheduler); + + updateStatus(ThingStatus.UNKNOWN); + + connectorExecutor.execute(connector); + this.connector = connector; + } + + @Override + public void dispose() { + final @Nullable ScheduledFuture future = this.refreshJob; + if (future != null) { + future.cancel(true); + } + + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + connector.close(); + this.connector = null; + } + + connectorExecutor.shutdown(); + } + + /** + * send action to pilight daemon + * + * @param action action to send + */ + public void sendAction(Action action) { + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + connector.sendAction(action); + } + } + + /** + * refresh config and status by requesting config and all values from pilight daemon + */ + public synchronized void refreshConfigAndStatus() { + if (thing.getStatus() == ThingStatus.ONLINE) { + final @Nullable ScheduledFuture refreshJob = this.refreshJob; + if (refreshJob == null || refreshJob.isCancelled() || refreshJob.isDone()) { + logger.debug("schedule refresh of config and status"); + this.refreshJob = scheduler.schedule(this::doRefreshConfigAndStatus, REFRESH_CONFIG_MSEC, + TimeUnit.MILLISECONDS); + } + } else { + logger.warn("Bridge is not online - ignoring refresh of config and status."); + } + } + + private void doRefreshConfigAndStatus() { + final @Nullable PilightConnector connector = this.connector; + if (connector != null) { + // the config is required for dimmers to get the minimum and maximum dim levels + connector.refreshConfig(); + connector.refreshStatus(); + } + } + + /** + * Processes a status update received from pilight + * + * @param status The new Status + */ + private void processStatus(Status status) { + final Integer type = status.getType(); + logger.trace("processStatus device '{}' type {}", status.getDevices().get(0), type); + + if (!DeviceType.SERVER.equals(type)) { + for (Thing thing : getThing().getThings()) { + final @Nullable ThingHandler handler = thing.getHandler(); + if (handler instanceof PilightBaseHandler) { + ((PilightBaseHandler) handler).updateFromStatusIfMatches(status); + } + } + } + } + + @Override + public Collection> getServices() { + return Collections.singleton(PilightDeviceDiscoveryService.class); + } + + /** + * Register discovery service to this bridge instance. + */ + public boolean registerDiscoveryListener(PilightDeviceDiscoveryService listener) { + if (discoveryService == null) { + discoveryService = listener; + return true; + } + return false; + } + + /** + * Unregister discovery service from this bridge instance. + */ + public boolean unregisterDiscoveryListener() { + if (discoveryService != null) { + discoveryService = null; + return true; + } + + return false; + } + + /** + * Processes a config received from pilight + * + * @param config The new config + */ + private void processConfig(Config config) { + for (Thing thing : getThing().getThings()) { + final @Nullable ThingHandler handler = thing.getHandler(); + if (handler instanceof PilightBaseHandler) { + ((PilightBaseHandler) handler).updateFromConfigIfMatches(config); + } + } + + final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService; + if (discoveryService != null) { + discoveryService.setConfig(config); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java new file mode 100644 index 0000000000000..d4d26e641c123 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightContactHandler.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.types.PilightContactType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightContactHandler} is responsible for handling a pilight contact. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightContactHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightContactHandler.class); + + public PilightContactHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + String state = status.getValues().get("state"); + if (state != null) { + updateState(CHANNEL_STATE, PilightContactType.valueOf(state.toUpperCase()).toOpenClosedType()); + } + } + + @Override + void updateFromConfigDevice(Device device) { + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { + logger.warn("A contact is a read only device"); + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java new file mode 100644 index 0000000000000..a0d18d9c822ee --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightDimmerHandler.java @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_DIMLEVEL; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Code; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.binding.pilight.internal.dto.Values; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightDimmerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightDimmerHandler extends PilightBaseHandler { + + private static final int MAX_DIM_LEVEL_DEFAULT = 15; + private static final BigDecimal BIG_DECIMAL_100 = new BigDecimal(100); + + private final Logger logger = LoggerFactory.getLogger(PilightDimmerHandler.class); + + private int maxDimLevel = MAX_DIM_LEVEL_DEFAULT; + + public PilightDimmerHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + BigDecimal dimLevel = BigDecimal.ZERO; + String dimLevelAsString = status.getValues().get("dimlevel"); + + if (dimLevelAsString != null) { + dimLevel = getPercentageFromDimLevel(dimLevelAsString); + } else { + // Dimmer items can can also be switched on or off in pilight. + // When this happens, the dimmer value is not reported. At least we know it's on or off. + String stateAsString = status.getValues().get("state"); + if (stateAsString != null) { + State state = OnOffType.valueOf(stateAsString.toUpperCase()); + dimLevel = state.equals(OnOffType.ON) ? BIG_DECIMAL_100 : BigDecimal.ZERO; + } + } + + State state = new PercentType(dimLevel); + updateState(CHANNEL_DIMLEVEL, state); + } + + @Override + protected void updateFromConfigDevice(Device device) { + Integer max = device.getDimlevelMaximum(); + + if (max != null) { + maxDimLevel = max; + } + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) { + Code code = new Code(); + code.setDevice(getName()); + + if (command instanceof OnOffType) { + code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF); + } else if (command instanceof PercentType) { + setDimmerValue((PercentType) command, code); + } else { + logger.warn("Only OnOffType and PercentType are supported by a dimmer."); + return null; + } + + Action action = new Action(Action.ACTION_CONTROL); + action.setCode(code); + return action; + } + + private BigDecimal getPercentageFromDimLevel(String string) { + return new BigDecimal(string).setScale(2).divide(new BigDecimal(maxDimLevel), RoundingMode.HALF_UP) + .multiply(BIG_DECIMAL_100); + } + + private void setDimmerValue(PercentType percent, Code code) { + if (PercentType.ZERO.equals(percent)) { + // pilight is not responding to commands that set both the dimlevel to 0 and state to off. + // So, we're only updating the state for now + code.setState(Code.STATE_OFF); + } else { + BigDecimal dimlevel = percent.toBigDecimal().setScale(2).divide(BIG_DECIMAL_100, RoundingMode.HALF_UP) + .multiply(BigDecimal.valueOf(maxDimLevel)).setScale(0, RoundingMode.HALF_UP); + + Values values = new Values(); + values.setDimlevel(dimlevel.intValue()); + code.setValues(values); + code.setState(dimlevel.compareTo(BigDecimal.ZERO) == 1 ? Code.STATE_ON : Code.STATE_OFF); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java new file mode 100644 index 0000000000000..21adaa309dd91 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightGenericHandler.java @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.PilightChannelConfiguration; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeRegistry; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.StateDescription; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightGenericHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightGenericHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightGenericHandler.class); + + private final ChannelTypeRegistry channelTypeRegistry; + + private final Map channelReadOnlyMap = new HashMap<>(); + + public PilightGenericHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) { + super(thing); + this.channelTypeRegistry = channelTypeRegistry; + } + + @Override + public void initialize() { + super.initialize(); + initializeReadOnlyChannels(); + } + + @Override + protected void updateFromStatus(Status status) { + for (Channel channel : thing.getChannels()) { + PilightChannelConfiguration config = channel.getConfiguration().as(PilightChannelConfiguration.class); + updateState(channel.getUID(), + getDynamicChannelState(channel, status.getValues().get(config.getProperty()))); + } + } + + @Override + void updateFromConfigDevice(Device device) { + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID channelUID, Command command) { + if (isChannelReadOnly(channelUID)) { + logger.debug("Can't apply command '{}' to '{}' because channel is readonly.", command, channelUID.getId()); + return null; + } + + logger.debug("Create update command for '{}' not implemented.", channelUID.getId()); + + return null; + } + + private State getDynamicChannelState(final Channel channel, final @Nullable String value) { + final @Nullable String acceptedItemType = channel.getAcceptedItemType(); + + if (value == null || acceptedItemType == null) { + return UnDefType.UNDEF; + } + + switch (acceptedItemType) { + case CoreItemFactory.NUMBER: + return new DecimalType(value); + case CoreItemFactory.STRING: + return StringType.valueOf(value); + case CoreItemFactory.SWITCH: + return OnOffType.from(value); + default: + logger.trace("Type '{}' for channel '{}' not implemented", channel.getAcceptedItemType(), channel); + return UnDefType.UNDEF; + } + } + + private void initializeReadOnlyChannels() { + channelReadOnlyMap.clear(); + for (Channel channel : thing.getChannels()) { + final @Nullable ChannelTypeUID channelTypeUID = channel.getChannelTypeUID(); + if (channelTypeUID != null) { + final @Nullable ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID, null); + + if (channelType != null) { + logger.debug("initializeReadOnly {} {}", channelType, channelType.getState()); + } + + if (channelType != null) { + final @Nullable StateDescription state = channelType.getState(); + if (state != null) { + channelReadOnlyMap.putIfAbsent(channel.getUID(), state.isReadOnly()); + } + } + } + } + } + + private boolean isChannelReadOnly(ChannelUID channelUID) { + Boolean isReadOnly = channelReadOnlyMap.get(channelUID); + return isReadOnly != null ? isReadOnly : true; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java new file mode 100644 index 0000000000000..179d6d2f83c59 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/handler/PilightSwitchHandler.java @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.handler; + +import static org.openhab.binding.pilight.internal.PilightBindingConstants.CHANNEL_STATE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.pilight.internal.dto.Action; +import org.openhab.binding.pilight.internal.dto.Code; +import org.openhab.binding.pilight.internal.dto.Device; +import org.openhab.binding.pilight.internal.dto.Status; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link PilightSwitchHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Röllin - Initial contribution + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class PilightSwitchHandler extends PilightBaseHandler { + + private final Logger logger = LoggerFactory.getLogger(PilightSwitchHandler.class); + + public PilightSwitchHandler(Thing thing) { + super(thing); + } + + @Override + protected void updateFromStatus(Status status) { + String state = status.getValues().get("state"); + if (state != null) { + updateState(CHANNEL_STATE, OnOffType.valueOf(state.toUpperCase())); + } + } + + @Override + void updateFromConfigDevice(Device device) { + } + + @Override + protected @Nullable Action createUpdateCommand(ChannelUID unused, Command command) { + if (command instanceof OnOffType) { + Code code = new Code(); + code.setDevice(getName()); + code.setState(command.equals(OnOffType.ON) ? Code.STATE_ON : Code.STATE_OFF); + + Action action = new Action(Action.ACTION_CONTROL); + action.setCode(code); + return action; + } + + logger.warn("A pilight switch only accepts OnOffType commands."); + return null; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java new file mode 100644 index 0000000000000..0a28a9d0fa18c --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/serializers/BooleanToIntegerSerializer.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.serializers; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +/** + * Serializer to map boolean values to an integer (1 and 0). + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +@NonNullByDefault +public class BooleanToIntegerSerializer extends JsonSerializer { + + @Override + public void serialize(@Nullable Boolean bool, @Nullable JsonGenerator jsonGenerator, + @Nullable SerializerProvider serializerProvider) throws IOException { + if (bool != null && jsonGenerator != null) { + jsonGenerator.writeObject(bool ? 1 : 0); + } + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java new file mode 100644 index 0000000000000..8867d83a82611 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/java/org/openhab/binding/pilight/internal/types/PilightContactType.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2021 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.pilight.internal.types; + +import org.openhab.core.library.types.OpenClosedType; + +/** + * Enum to represent the state of a contact sensor in pilight + * + * @author Jeroen Idserda - Initial contribution + * @author Stefan Röllin - Port to openHAB 2 pilight binding + * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery + */ +public enum PilightContactType { + OPENED, + CLOSED; + + public OpenClosedType toOpenClosedType() { + return this.equals(PilightContactType.OPENED) ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + } +} diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..2540db4c312a0 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,11 @@ + + + + Pilight Binding + The pilight binding allows openHAB to communicate with a pilight instance. Pilight is a service used to + control 'Click On Click Off' devices like 433 MHz remote controlled sockets a cheap way, e.g. by using a Raspberry Pi + with corresponding 433 MHz sender. + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml new file mode 100644 index 0000000000000..84809bcf447db --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/config/config.xml @@ -0,0 +1,15 @@ + + + + + + + The name of the pilight device. + + + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties new file mode 100644 index 0000000000000..b2b8e8ed5f750 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/i18n/pilight_de.properties @@ -0,0 +1,38 @@ +# binding +binding.pilight.name = Pilight Binding +binding.pilight.description = Das pilight-Binding ermglicht openHAB die Kommunikation mit einer pilight-Instanz. Pilight ist ein Dienst, der verwendet wird, um 'Click On Click Off'-Gerte wie bspw. 433 MHz Funksteckdosen auf kostengnstige Weise zu steuern, z.B. durch Verwendung eines Raspberry Pi mit entsprechendem 433 MHz Sender. + +# thing types +thing-type.pilight.bridge.label = Pilight Bridge +thing-type.pilight.bridge.description = Verbindung zwischen openHAB und einem pilight Daemon. + +thing-type.pilight.contact.label = Pilight Kontakt +thing-type.pilight.contact.description = Pilight Kontakt + +thing-type.pilight.dimmer.label = Pilight Dimmer +thing-type.pilight.dimmer.description = Pilight Dimmer + +thing-type.pilight.switch.label = Pilight Schalter +thing-type.pilight.switch.description = Pilight Schalter + +thing-type.pilight.generic.label = Generisches pilight Gert +thing-type.pilight.generic.description = Gert bei dem die Kanle dynamisch hinzugefgt werden. + +# thing type config description +thing-type.config.pilight.bridge.ipAddress.label = IP-Adresse +thing-type.config.pilight.bridge.ipAddress.description = Lokale IP-Adresse oder Hostname des pilight Daemons. +thing-type.config.pilight.bridge.port.label = Port +thing-type.config.pilight.bridge.port.description = Port des pilight Daemons. +thing-type.config.pilight.bridge.delay.label = Verzgerung +thing-type.config.pilight.bridge.delay.description = Verzgerung (in Millisekunden) zwischen zwei Kommandos. Empfohlener Wert ohne Bandpassfilter: 1000 und mit Bandpassfilter zwischen 200 und 500. + +thing-type.config.pilight.device.name.label = Name +thing-type.config.pilight.device.name.description = Name des pilight Gerts + +# channel types +channel-type.pilight.contact-state.label = Status +channel-type.pilight.contact-state.description = Status des pilight Kontakts +channel-type.pilight.switch-state.label = Status +channel-type.pilight.switch-state.description = Status des pilight Schalters +channel-type.pilight.dimlevel.label = Dimmerwert +channel-type.pilight.dimlevel.description = Wert des pilight Dimmers diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml new file mode 100644 index 0000000000000..e52c70a6021bb --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,39 @@ + + + + + + Pilight Bridge which connects to a Pilight instance. + + + - + + + + + + The IP or host name of the Pilight instance. + network-address + + + + Port of the Pilight daemon. You must explicitly configure the port in the Pilight daemon config or + otherwise a random port will be used and the binding will not be able to connect. + + 5000 + + + + Delay (in millisecond) between consecutive commands. Recommended value without band pass filter: 1000. + Recommended value with band pass filter: somewhere between 200-500. + 500 + true + + + + + + diff --git a/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml new file mode 100644 index 0000000000000..c55db4011e493 --- /dev/null +++ b/bundles/org.openhab.binding.pilight/src/main/resources/OH-INF/thing/devices.xml @@ -0,0 +1,96 @@ + + + + + + + + + + Pilight Switch + + + + + + + + + + + + + + + Pilight Contact + + + + + + + + + + + + + + + Pilight Dimmer + + + + + + + + + + + + + + + Pilight Generic Device + + + + + + Contact + + State of Pilight Contact. + + + + + String + + + + + + The Property of the Device. + true + + + + + + Number + + + + + + The Property of the Device. + true + + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 7132c692ac7e0..909b8d78d80eb 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -239,6 +239,7 @@ org.openhab.binding.paradoxalarm org.openhab.binding.pentair org.openhab.binding.phc + org.openhab.binding.pilight org.openhab.binding.pioneeravr org.openhab.binding.pixometer org.openhab.binding.pjlinkdevice From 8a4b87e04e96c008882585a9adf26e9ffd270d54 Mon Sep 17 00:00:00 2001 From: Stefan Roellin Date: Wed, 17 Feb 2021 21:06:34 +0100 Subject: [PATCH 011/118] [mpd] fix invalid volume (#10166) Fixes #10142. Signed-off-by: Stefan Roellin Also-by: Jan N. Klug --- .../openhab/binding/mpd/internal/handler/MPDHandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/handler/MPDHandler.java b/bundles/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/handler/MPDHandler.java index eecdf8eb2ff65..21ddbf2fcaece 100644 --- a/bundles/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/handler/MPDHandler.java +++ b/bundles/org.openhab.binding.mpd/src/main/java/org/openhab/binding/mpd/internal/handler/MPDHandler.java @@ -254,7 +254,11 @@ private void updateChannel(String channelID, State state) { @Override public void updateMPDStatus(MPDStatus status) { volume = status.getVolume(); - updateChannel(CHANNEL_VOLUME, new PercentType(status.getVolume())); + if (volume < 0 || volume > 100) { + updateChannel(CHANNEL_VOLUME, UnDefType.UNDEF); + } else { + updateChannel(CHANNEL_VOLUME, new PercentType(volume)); + } State newControlState = UnDefType.UNDEF; switch (status.getState()) { From 0853aa7ceac14fceaddda43c1d87538d2e378b75 Mon Sep 17 00:00:00 2001 From: Jan Gustafsson Date: Thu, 18 Feb 2021 09:33:22 +0100 Subject: [PATCH 012/118] [tellstick] Fix for #9841, adding support for Tellstick local API. (#10020) * Fix for #9841. Signed-off-by: Jan Gustafsson --- .../org.openhab.binding.tellstick/README.md | 53 +++- .../doc/znet.jpeg | Bin 0 -> 9764 bytes .../internal/TellstickBindingConstants.java | 21 +- .../internal/TellstickHandlerFactory.java | 16 + .../conf/TelldusLiveConfiguration.java | 2 +- .../conf/TelldusLocalConfiguration.java | 25 ++ .../discovery/TellstickDiscoveryService.java | 24 +- .../handler/TelldusDevicesHandler.java | 122 ++++++-- .../internal/live/xml/LiveDataType.java | 10 +- .../local/TelldusLocalBridgeHandler.java | 290 ++++++++++++++++++ .../local/TelldusLocalDeviceController.java | 282 +++++++++++++++++ .../internal/local/TelldusLocalException.java | 46 +++ .../local/dto/LocalDataTypeValueDTO.java | 51 +++ .../local/dto/TelldusLocalResponseDTO.java | 40 +++ .../local/dto/TellstickLocalDeviceDTO.java | 115 +++++++ .../local/dto/TellstickLocalDevicesDTO.java | 36 +++ .../local/dto/TellstickLocalSensorDTO.java | 130 ++++++++ .../dto/TellstickLocalSensorEventDTO.java | 43 +++ .../local/dto/TellstickLocalSensorsDTO.java | 36 +++ .../main/resources/OH-INF/thing/bridge.xml | 31 +- .../main/resources/OH-INF/thing/devices.xml | 1 + .../main/resources/OH-INF/thing/sensor.xml | 11 +- 22 files changed, 1317 insertions(+), 68 deletions(-) create mode 100644 bundles/org.openhab.binding.tellstick/doc/znet.jpeg create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLocalConfiguration.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalBridgeHandler.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalDeviceController.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalException.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/LocalDataTypeValueDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TelldusLocalResponseDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDeviceDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDevicesDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorEventDTO.java create mode 100644 bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorsDTO.java diff --git a/bundles/org.openhab.binding.tellstick/README.md b/bundles/org.openhab.binding.tellstick/README.md index 81f542e9f7833..72e2ba7e5e2a3 100644 --- a/bundles/org.openhab.binding.tellstick/README.md +++ b/bundles/org.openhab.binding.tellstick/README.md @@ -12,6 +12,10 @@ The latest versions have also implemented Z-Wave as transmission protocol which Tellstick Duo with device

+

+Tellstick Znet lite v2 +

+ ## Supported Things This binding supports the following thing types: @@ -24,6 +28,7 @@ Additionally the binding have two types of bridge things which correspond to ava * *Telldus Core Bridge* - Oldest API, used by USB devices. `telldus-core` * *Telldus Live Bridge* - Telldus Cloud service, all devices with online access. `telldus-live` +* *Telldus Local Bridge* - Telldus Local API, Tellstick Net v2/Tellstick ZNet Lite v1/v2. `telldus-local` ***Switchbased sensors workaround*** @@ -32,11 +37,12 @@ Additionally the binding have two types of bridge things which correspond to ava ## Discovery -Devices which is added to *Telldus Core* and *Telldus Live* can be discovered by openHAB. +Devices which is added to *Telldus Core*, *Telldus Live* and *Telldus Local* can be discovered by openHAB. When you add this binding it will try to discover the *Telldus Core Bridge*. If it is installed correct its devices will show up. -If you want to use the *Telldus Live* its bridge, *Telldus Live bridge* need to be added manually. + +If you want to use the *Telldus Live* or *Telldus Local*, their bridges, *Telldus Live bridge* or *Tellstick Local*, needs to be added manually. ## Binding Configuration @@ -54,13 +60,13 @@ Use the option `repeat` for that. Default resend count is 2. ### Bridges -Depending on your tellstick device type there is different ways of using this binding. -The binding implements two different API: +Depending on your Tellstick device type there is different ways of using this binding. +The binding implements three different APIs: **1)** *Telldus Core* which is a local only interface supported by USB based device.
-**2)** *Telldus Live* which is a REST based cloud service maintained by Telldus.
+**2)** *Telldus Live* which is a REST based cloud service maintained by Telldus. +**3)** *Telldus Local* which is a REST based local service maintained by Telldus. +
-> Not implemented yet but supported by some new devices, contributions are welcome. [API documention.](https://api.telldus.net/localapi/api.html)
-> **3)** *Local Rest API* is a local API which would work similar to Telldus Live but local. Depending on your Tellstick model, different bridge-types are available: @@ -110,6 +116,36 @@ Optional: - **refreshInterval:** How often we should contact *Telldus Live* to check for updates (in ms) +#### Telldus Local Bridge + +To configure Telldus Local you need to know the local IP address of your Tellstick device and also request an access token. + +Goto this page: + +and follow steps 1), 2) and 3) to generate an access token. + +In step 2) when you authenticate the application in your favorite browser, choose the options '1 year' and 'Auto renew access'. + +Copy the 'token' returned in Step 3) and use that as accessToken in the local bridge config. + +``` +"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6IkV4YW1wbGUgYXBwIiwiZXhwIjoxNDUyOTUxNTYyfQ.eyJyZW5ldyI6dHJ1ZSwidHRsIjo4NjQwMH0.HeqoFM6-K5IuQa08Zr9HM9V2TKGRI9VxXlgdsutP7sg" +``` + + +``` +Bridge tellstick:telldus-local:3 "Tellstick Local ZWave" [ipAddress="x.y.z.w" , accesToken= "XYZ...W"] +``` + +Required: + +- **ipAddress:** Local IP address of your Tellstick device +- **accessToken:** Access Token + +Optional: + +- **refreshInterval:** How often we should contact *Telldus Local* to check for updates (in ms) + ## Channels Actuators (dimmer/switch) support the following channels: @@ -194,6 +230,9 @@ Bridge tellstick:telldus-core:1 "Tellstick Duo" [resendInterval=200] { Bridge tellstick:telldus-live:2 "Tellstick ZWave" [refreshInterval=10000, publicKey="XXXXXXXX", privateKey="YYYYYY", token= "ZZZZZZZZ", tokenSecret="UUUUUUUUUU"] { sensor OutsideSensor2 [protocol="fineoffset",model="temperaturehumidity",name="temperaturehumidity:120",deviceId="120_temperaturehumidity_fineoffset"] } +Bridge tellstick:telldus-local:3 "Tellstick Local ZWave" [ipAddress="192.168.50.17" , accesToken= "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImF1ZCI6IkV4YW1wbGUgYXBwIiwiZXhwIjoxNDUyOTUxNTYyfQ.eyJyZW5ldyI6dHJ1ZSwidHRsIjo4NjQwMH0.HeqoFM6-K5IuQa08Zr9HM9V2TKGRI9VxXlgdsutP7sg"] { + sensor OutsideSensor3 [protocol="fineoffset",model="temperaturehumidity",name="temperaturehumidity:120",deviceId="120_temperaturehumidity_fineoffset"] +} ``` ### tellstick.items diff --git a/bundles/org.openhab.binding.tellstick/doc/znet.jpeg b/bundles/org.openhab.binding.tellstick/doc/znet.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8d324d55a25b15d6e0a8b8f1cdb2f9ecf4104e11 GIT binary patch literal 9764 zcmdUUcT`jBvvxuWRS3OHFo1MIl@6izA|N20(2?E|q<5*(rHXWE7C`ABMLGxyC=hy= z-UV*X@#ycY^?m$tzkj}ace2)g_slcT%uB5;f!J7b+PhfUK)iUkU=S8hcRME=h?24bL|Ic#0pcYDW95LjdYV77^YDd; zLxf{Nn z02&bxod|gS5kLa~0?^P=ss85xqNAbW#X>2mQF4^>KPmtKh>3;)Lcg8{;G^VdV017_ zbe{ij#Q*;_%jbjs;;)m#;KZ9^!L96}hLx6ZI5MVc! zLdAyfmGv+0upD05jBfiJcO5C-2=$%Su!$R((O&aEc|1ob#l3MO5T`I1XN!^4*PhS=1&RX5JTR-srdD(2c&UzCPfG*R2 zUd=9JXR?y}rKP8qPJr@8sJ*YG;Zo_V&B?EgdJfa^xluPF1)377_W8YhY@XUVJuVm@ zxe*AePdb`aj_=2<{8~MGj2wWfuGe;cwc5$@3-Onkt*F2rEYc*`WxBYQg#^ZgX2vzUoB$W2Tg>ki{zC+; zTxxbsE_~f%X!EeHbMjEw*rYf}9Q6a>z8n0?73H;wT=248iTl8wh|!kFjM0Vz0N@0n zb2jr#H%5-7{YmSVGt;JjDqR}&1@%$g?dSEY0TgQTh_;ixLsH+2$UxIn<|EQi&uPOx zCgaHVDgtn#0mQOlZK`VFYW+ixjva+54&^J)+WDs#t*UDw{{;6Z!0(@`faM}?SERW}tPiNmcMpRXG)Ea;anbiMA!8VpJ{4_cN5o}XS~t>($J zlr8TyimA;gPYek=AD$UYUrNOLD$q3-cVk4MsTx@7-80}c^kRHtLu1LpzOpYss4@I? zQh35oy>?Ojp1jz8Ap)N_HU+`+L;#+~b4LpPMraxG%Nbx|FZXlr*wkosqn!0s$=H?S zVeE%08^XrbE&iht6Q0~*y|}OUwyHi^8Ar!k(S0Bq?0?KBKl4f8)d#Bw2H2x>ud1Cj zEFOThy@b-bc<@}>R6(r77>dzo7#+8sZoZcKtCme3Y~7;8ShW~Y+H3F_9g?v}#9iN+uflKS+N zN*-oi{>DOpLqs_Fp9HY(Ru(MgQ4ln(ZM&6k+CDn9s_mQ0HZgQa-%QZ{d^V?$EBa-^ zE~Q&q38DKA8_trf1p#9OgOwT6U@DZnx(2n>zUg|7s}e&mvnaNhi2q>)pv8`?thOBV zYuhmUOxQg${dA!y7KjjIXmk|pRi2N+|SIW<=b#7eiamJZ9l{!S?m%6rp+5azE{^AN-fLZb5`cLxV!DQIEDQ08VCrWYdkR|sisLf$6Ug|ipptB0{ zeGVLI?a_jlgSs?TL%#pHFW1@}y z@RV3kV-?w`kfJyxHitP)n=IeAddlUkt~eE-5wdc)6qh;S!)1fIoG}eq_E?083}b#; z!f)RcqktyzkyPv&#Y^=`OiAD+^-@t3wH7Wsyo!V-^8BohKN@23ZYP9ztHrDR2p@~a zE94%rbOWhY?3lW&l8Pe5f@cJU?`?H_k_}&v-ke=Rsk;0YkDXj)TCjbgEsxxuZ9(r6 zQzRv)dnfhIK!KaFJHlNArwmK+XSIDA^%~L?Z{>UY-}f%zXTG7oG_{%{6^r^?Ii%5| z`HXGCn~F9#Sy1H~0J>B%et264E3GF9LHm3s`H{|GEUgY4m(?kYWWsCeFO=xj%iNfL z8|H62CaS4iXH`g$%-kfMZ`oE$g_`@QBv2L+M4-YdDFOJ8B_dRsre}V^yq_q2k64;rv`OHG-7LY=038IhoUO zs-P#%;&XXz%-a*8rmG#Ayw)`}G3t!rbK*)X-!gW(&70QnlanwiImyVQX^>e^E;TjQ zKmVnAJ>4`d2a?PN*kNmHYuB-GNm5Rd`}{N>YU(FRSB!F)aEU(`{mc)XR;2pMBpqXj zL`K7?WhKnp9>v?7A|a}R=rTfXCNbC@aZfiK32##*iW@wl3HKJGRrPgc>)PI&wU^o3 z_KLX%uro*GzUNoJH>!F^j<{myt?QF2iQadH(FXL|0s;a*-U&tWCESN)=Q(DY(p>RL zMsetuetk2RmN%zM^bOs9RUSMw5x~YyQEog`n7HC9$hKwC163s|aZNg)kNeX1@mY4Z z%PZI3Dwk)Kifu*Oltf1=@5UEshs5RZ7&+Yd+o`?NWTKF+LR8c(+x7+6?G(B+h-Z;( z#*EnKMN`5AHshp^5^JZZAz{gvY7MPEcoUM|+EMOwktd0z&!GtzEJf$*aK+CWI#=Rx z>I?&3)P2&$-5fqiVTcO_detj&&TK@@q8r%*Mg5QVVS#R6Uj%|sVFtkEr|_tfUf`V! zQrq4+vlYcWVq|0PU2Ml?^|>8dq|{B6aFKzkBrHS#AUZk*5Dn{h2}Jef7(~39=wKc` z24X4FJ21GGOVHmn1l8wD0x$h37u#gDvnk=~w6stk?NdrMQRy7p4%{N&>GWJRaQpBo+GIc4qNq%^9_VQBgV_qxvwLq3@VN-_Ev* zo1L&hcF~ka_V*w6e|}JZ;?_VVpNzchu z%&;SJvY;?)mPA|ht?f#E*aYAUjfcBohy6lUhkbc@btkB0>&tFd19MH%x~U(BD{iWq zM1j^w%;DaTK`xwfQ*#BRY}jN-yqc|9{wbeIh~51QWf9gCh{5K$n$caqhg!a>o*q2Y zw0)$rjNC83M8x+Bmc0{^2sLF{BvIQ&QPU96FJawH#FuLNA?;^3tQI*j@Kqs{#(X^~ zr?Td-clgVsFV*?d(+8j$WCRn|9|*HioCYLvQVp@`_&d4v&4Yv{`>7?#6dmNB<2fsbW|y+gc#09)6#~ zQ>t25(A)R}`XSOOBP+479CJ$NjGsL#CgbaRJK<(n8QzC{<5Z_#bv2kkOvRnApU4%~~nW@A(9rA`2sFdT;d%0L_*~jMN zpUR%g!su`jk2hx??GU}-v*DVFZm0?1#MIWI?5WBH@-!NJ zrCB3@lY%_i{HzZunSEP|$ZG&i+NQOKHY56!(zsEFS>=}@)3Xtls`!2hZZlcl0R4HBx7A7yFAttT& zxUru(_4rzq*EbX8QtXU|(qi6CXsX;%`^**;IF*2^6C7aF!vI9Rw!lD@xWAtcAb^Mm z%mCLicZEoq1sA|HlWN;XcjpuLem7H+4A+3Mxw%95r=7?x4?miSmDHB~bYKR`(Mq3?ShOy5gC~EQ!R~ST@ zb$u}^quWoS1fH#2IVfnN92^xL@uf#Yb{zq>c7B6N~Z~tTPn-tOR ztM?fcO`Q=L@k*MxrYmBE=2&od45{N#@DBqQkJfx&v3~j_jG+O8lORelJXs3LGzIt0 z1nz@}BJ3+N#hrtdY?uqwSn)+Z70Rh>4_>srNfYh4dY?tn)KLI4f1?CNRmJmC9#VRO7%2&@%?pkX-TV5 zK(V@7HJgP&?GS?Q%%KU_M|6Rtn{=RHIPXLkJSTU*rCNz0 zHbO`M@52A3##bU05fgiT#7;8uG#XQWsS4Wq!>Q2lgyae1G}4Qvj&ix1bY$T6;#>Vk z9oAKxPRwFS;Z^2Hp5-SFvchVK*{zpiR2H!Y93^h9l9W+n+KZ+4!f3Ex2ozXP$K!fn zN+|j?VvW=n57aJi{{VH&6lrz{)vK2BW{PgV-p*Fluazn22=q%BN+Tj#j`7(z&5Z3E zq2;0kvl0wEqbb=6yHcb1>?Vn(YWT$cR!gi~D0~f#*iMvXpq|IICqNd+?Lzu<`s|Ul!?MsR4#bK zEOl%>339?Up9d^jIH8}VKyN$NA~?e{@2CbhE+mF4t4zDtF>p~c*K^DDr_(w28Z|rJ zG25S+L6-~fW_u_*MW%TxTB`<3itH`{U5=q08f14PU=bxj9^Cy#2ChV4CC_(T42Hr3 zf?~B!tT=Y{$h5$)pt|=)Bvh$iRoL8BAdaC;s~OnxFBT6v=yqu&5jv5fBpdY7%j<5- zkoIiHDIrQv8{y;Zp`%$qy9r|6mS;UT`FL#G4olxH%%7%@yS`W?JcGw?dXjs7KF&oV z$NseHJ|vX8hJDO!gX+?CaK{*lZqbB1Y`4|;NFOeX9)gC}t(UQ0yc3BP%|h@k(zmXO zr|RywN3#wDjBt!wkrLgeX9Q*)xi^HI^=q&G8~eP@TwPQCQq=m*^GC7oDYO6%)tlrtb+=rT<+epd)ms_Nil)< zV(XAzR(Typ^X(d7Er}|Kf4|EG{MBNC4~E75-@c1~42S+^NDwn z&CRs4p_Yr)7L`9P`U14+4+L_@4#EV>c3OP7lOM1Dal!o{e;032gX=^HxC8P*GhHYZ z>i=!{c(+w_l@CMcDyV`5h|TEcP#)#{+b~|?L%zoWPjTqrs}8K>ezW8#o9usr`3Unh zWt>e{PQ=8`2ztR{79reGrTq^l(c(|rl<{4C>#V4?c-xIqUYVzVQaBdh+KWHGQxbBD ztXgl6sr?q!(3{El2HBBpQ#djeBRQ7L`cijzU9~9=Iz~os?lue zYpwWj>WU+Q`xBmgok9+AE0d)ie}%54@4kPu1ow03=L+J$syc<4b8?TZxCbwW;-2|4 zt~E06CQ*qp?<#p(&JvVkL_82j?h+F?^)!jxmS0c8j_@}=CzIUj8w>*BB zP{;bnk_|R?L``eFBI`y#XF>5IBk3)Zc^!UH%aF*jh(-cMyt23a;slE9^=mT5*8l^^ zoi&E)lf7hIN6>D%AtP>X$*9FsW`2tJ z`MFck0oPFYW^@nD%1^o#)@jY6h=d?%v-esuQb?G|sp#ZGiRg?)NZWTV2_|zSn(3EB zNE;0L6m}mdYle>Doh!z0bSu;l1O(0#wF!j9z z=MBYov%;_r!zh*UO}*kV?HjziI?dWPw=AT_s0*-FJGf_J*pX;3ycI1BpuR<8mDHA& z+%OHP>5ISTbqylaM^y`HJ3AiTKx-xH@B;INSJXCJq(+cO+hVt{nyu2h-ob#IIzt!)M%}!8=>nDFYL7sF&4)aAeCC^-|XftkKx+!opccjF- zz+eYSz)B)Fk zdy$NdcBd%${B>Q!A-BuA`vIUIZEF8emQ+JvF9hX{~tZ{S!7^Aa?=% zj%<(l5ITK3oZ?6u%`#zz&_f)TsA}IH|sNO!L?Xg8a?U6Z}j?UX^;gG*;| zMrSEB|NVDNQEghv6(7oE-@wm~U4xw0fDbZP(iV;lF6c3E?s%tq07DMr(cqPx8X&N{Q}hkOl!+e zv^L4D*SvzM6=?xs*u+H+ZTGd=v{_Sj(Fp;1pfdIF!Sop&`FsxwmREiyws#ENPFaGM zAVHo-;U~z5v~q!HO)W}*qXYS3NPMV%=L+p$9_K}6{#xft5jD@k{cAuxZhc=*#zN_s zT;lj;Q=*~F3q2=#XX517O^o-KnIObPCd^Vt<(a-mjv>`A?RXjHbOeJ$NEC1Bm&d~B z%qOu))F$U}Ug}aPt!TaP?NopHEiPAq+b@fN#G|U=LA@4nT)G$=kqieRbo;hc4_TiM z9)Ws`0h3g4OJc}#1icS*>+1yv>#)vGnWpi0$LKSpk6U#In-8Jv!=wqF+wBeJDQa9Y z_?}WJYC<}!@%}0^o|JI1U5(nerSCABxY^=xsj~_|@{PE(gk32w2imNM`i*RDA9PrXto;DNBJAL>d>T*H_JAe#pA z1! zY*P42D#Tn}Noy*&IPrVFBgQiNTQaDEJZqcz_jC|DR6V`Eii&oh=}ID^1`->+^o^koM|I~Aq%c>QUU#ry@6tkhmpZnHQuol1_^V0 zZy<@yR%DL$`_6Pp>lvw5=DkI46d)p68>OcWg)E-~cF8a+)S_ICwZ>9PKa>L29@-6W z(vH9%D7;9J7HS}C3xS~-$yy=b*!`Gy(am(v!;9rx@QlPO-Q_YeqcS@$I(wj{O>dQ+ z{M?HEdzMN4%Q2i*r!{Uz)z0zTbDF zO65<8PJ60Hj#QN!<56-A^-#$K;-rMKR>fuyyuZ4`Ug1Zb66Kw@jPrs9)7Ohr^2?jL z&J0b>Ld{R9Sz~v3)9g&o-@B}jrV7~ z_WXKqbVwNqpGUKrsdsj1#?{F4r+$9tpq3sMkGqT=_vgrY*T&(^SSiULN zRADR94=WJ*tWpQA@)?sYTSeM_Lz6)v?{Tjpnw)|jM9s(H`}#`cEs-bMMw2HQ;CF(1 zutbr2O#Nf1!Cc)1od_;r+Es5F0UQhiOyhjF; literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickBindingConstants.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickBindingConstants.java index a8690ec827a66..1f62da26158ee 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickBindingConstants.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickBindingConstants.java @@ -14,10 +14,7 @@ import static org.openhab.core.library.unit.MetricPrefix.*; -import java.util.Collections; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.measure.Unit; import javax.measure.quantity.Angle; @@ -65,6 +62,7 @@ public class TellstickBindingConstants { public static final String DEVICE_ISDIMMER = "dimmer"; public static final String BRIDGE_TELLDUS_CORE = "telldus-core"; public static final String BRIDGE_TELLDUS_LIVE = "telldus-live"; + public static final String BRIDGE_TELLDUS_LOCAL = "telldus-local"; public static final String DEVICE_SENSOR = "sensor"; public static final String DEVICE_WINDSENSOR = "windsensor"; public static final String DEVICE_RAINSENSOR = "rainsensor"; @@ -82,6 +80,7 @@ public class TellstickBindingConstants { public static final ThingTypeUID TELLDUSBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_TELLDUS_CORE); public static final ThingTypeUID TELLDUSCOREBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_TELLDUS_CORE); public static final ThingTypeUID TELLDUSLIVEBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_TELLDUS_LIVE); + public static final ThingTypeUID TELLDUSLOCALBRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, BRIDGE_TELLDUS_LOCAL); // List of all Channel ids public static final String CHANNEL_DIMMER = "dimmer"; public static final String CHANNEL_STATE = "state"; @@ -97,13 +96,11 @@ public class TellstickBindingConstants { public static final String CHANNEL_AMPERE = "ampere"; public static final String CHANNEL_LUX = "lux"; - public static final Set SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections.unmodifiableSet( - Stream.of(TELLDUSCOREBRIDGE_THING_TYPE, TELLDUSLIVEBRIDGE_THING_TYPE).collect(Collectors.toSet())); - public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Collections - .unmodifiableSet(Stream.of(DIMMER_THING_TYPE, SWITCH_THING_TYPE, SENSOR_THING_TYPE, RAINSENSOR_THING_TYPE, - WINDSENSOR_THING_TYPE, POWERSENSOR_THING_TYPE).collect(Collectors.toSet())); - public static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream - .of(DIMMER_THING_TYPE, SWITCH_THING_TYPE, SENSOR_THING_TYPE, RAINSENSOR_THING_TYPE, WINDSENSOR_THING_TYPE, - POWERSENSOR_THING_TYPE, TELLDUSCOREBRIDGE_THING_TYPE, TELLDUSLIVEBRIDGE_THING_TYPE) - .collect(Collectors.toSet())); + public static final Set SUPPORTED_BRIDGE_THING_TYPES_UIDS = Set.of(TELLDUSCOREBRIDGE_THING_TYPE, + TELLDUSLIVEBRIDGE_THING_TYPE); + public static final Set SUPPORTED_DEVICE_THING_TYPES_UIDS = Set.of(DIMMER_THING_TYPE, + SWITCH_THING_TYPE, SENSOR_THING_TYPE, RAINSENSOR_THING_TYPE, WINDSENSOR_THING_TYPE, POWERSENSOR_THING_TYPE); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(DIMMER_THING_TYPE, SWITCH_THING_TYPE, + SENSOR_THING_TYPE, RAINSENSOR_THING_TYPE, WINDSENSOR_THING_TYPE, POWERSENSOR_THING_TYPE, + TELLDUSCOREBRIDGE_THING_TYPE, TELLDUSLIVEBRIDGE_THING_TYPE, TELLDUSLOCALBRIDGE_THING_TYPE); } diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickHandlerFactory.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickHandlerFactory.java index 3897fcee9bd65..266e7c8bd3d3f 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickHandlerFactory.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/TellstickHandlerFactory.java @@ -16,19 +16,24 @@ import java.util.Hashtable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.tellstick.internal.core.TelldusCoreBridgeHandler; import org.openhab.binding.tellstick.internal.discovery.TellstickDiscoveryService; import org.openhab.binding.tellstick.internal.handler.TelldusBridgeHandler; import org.openhab.binding.tellstick.internal.handler.TelldusDevicesHandler; import org.openhab.binding.tellstick.internal.live.TelldusLiveBridgeHandler; +import org.openhab.binding.tellstick.internal.local.TelldusLocalBridgeHandler; import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.net.http.HttpClientFactory; 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.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; @@ -37,11 +42,18 @@ * handlers. * * @author Jarle Hjortland - Initial contribution + * @author Jan Gustafsson - Adding support for local API */ @Component(service = ThingHandlerFactory.class, configurationPid = "binding.tellstick") public class TellstickHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(TellstickHandlerFactory.class); private TellstickDiscoveryService discoveryService = null; + private final HttpClient httpClient; + + @Activate + public TellstickHandlerFactory(@Reference HttpClientFactory httpClientFactory) { + this.httpClient = httpClientFactory.getCommonHttpClient(); + } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -68,6 +80,10 @@ protected ThingHandler createHandler(Thing thing) { TelldusLiveBridgeHandler handler = new TelldusLiveBridgeHandler((Bridge) thing); registerDeviceDiscoveryService(handler); return handler; + } else if (thing.getThingTypeUID().equals(TELLDUSLOCALBRIDGE_THING_TYPE)) { + TelldusLocalBridgeHandler handler = new TelldusLocalBridgeHandler((Bridge) thing, httpClient); + registerDeviceDiscoveryService(handler); + return handler; } else if (supportsThingType(thing.getThingTypeUID())) { return new TelldusDevicesHandler(thing); } else { diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLiveConfiguration.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLiveConfiguration.java index 32528e7c11593..e334c0b9fa9fc 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLiveConfiguration.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLiveConfiguration.java @@ -14,7 +14,7 @@ /** * Configuration class for {@link TellstickBridge} bridge used to connect to the - * Tellus Live service. + * Telldus Live service. * * @author Jarle Hjortland - Initial contribution */ diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLocalConfiguration.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLocalConfiguration.java new file mode 100644 index 0000000000000..61e315ad0b9e7 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/conf/TelldusLocalConfiguration.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.conf; + +/** + * Configuration class for {@link TellstickBridge} bridge used to connect to the + * Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TelldusLocalConfiguration { + public String ipAddress; + public String accessToken; + public long refreshInterval; +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/discovery/TellstickDiscoveryService.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/discovery/TellstickDiscoveryService.java index 424e805fdc35a..59ab0403784ec 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/discovery/TellstickDiscoveryService.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/discovery/TellstickDiscoveryService.java @@ -22,6 +22,8 @@ import org.openhab.binding.tellstick.internal.live.xml.LiveDataType; import org.openhab.binding.tellstick.internal.live.xml.TellstickNetDevice; import org.openhab.binding.tellstick.internal.live.xml.TellstickNetSensor; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDeviceDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorDTO; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -141,6 +143,14 @@ private ThingUID getThingUID(Bridge bridge, Device device) { thingUID = new ThingUID(TellstickBindingConstants.SWITCH_THING_TYPE, bridge.getUID(), device.getUUId()); } + } else if (device instanceof TellstickLocalDeviceDTO) { + if ((((TellstickLocalDeviceDTO) device).getMethods() & JNA.CLibrary.TELLSTICK_DIM) > 0) { + thingUID = new ThingUID(TellstickBindingConstants.DIMMER_THING_TYPE, bridge.getUID(), + device.getUUId()); + } else { + thingUID = new ThingUID(TellstickBindingConstants.SWITCH_THING_TYPE, bridge.getUID(), + device.getUUId()); + } } break; default: @@ -163,7 +173,7 @@ private ThingTypeUID findSensorType(Device device) { } else { sensorThingId = TellstickBindingConstants.SENSOR_THING_TYPE; } - } else { + } else if (device instanceof TellstickNetSensor) { TellstickNetSensor sensor = (TellstickNetSensor) device; if (sensor.isSensorOfType(LiveDataType.WINDAVERAGE) || sensor.isSensorOfType(LiveDataType.WINDDIRECTION) || sensor.isSensorOfType(LiveDataType.WINDGUST)) { @@ -175,6 +185,18 @@ private ThingTypeUID findSensorType(Device device) { } else { sensorThingId = TellstickBindingConstants.SENSOR_THING_TYPE; } + } else { + TellstickLocalSensorDTO sensor = (TellstickLocalSensorDTO) device; + if (sensor.isSensorOfType(LiveDataType.WINDAVERAGE) || sensor.isSensorOfType(LiveDataType.WINDDIRECTION) + || sensor.isSensorOfType(LiveDataType.WINDGUST)) { + sensorThingId = TellstickBindingConstants.WINDSENSOR_THING_TYPE; + } else if (sensor.isSensorOfType(LiveDataType.RAINRATE) || sensor.isSensorOfType(LiveDataType.RAINTOTAL)) { + sensorThingId = TellstickBindingConstants.RAINSENSOR_THING_TYPE; + } else if (sensor.isSensorOfType(LiveDataType.WATT)) { + sensorThingId = TellstickBindingConstants.POWERSENSOR_THING_TYPE; + } else { + sensorThingId = TellstickBindingConstants.SENSOR_THING_TYPE; + } } return sensorThingId; } diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/handler/TelldusDevicesHandler.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/handler/TelldusDevicesHandler.java index b46412e426539..e1b57874cc7e5 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/handler/TelldusDevicesHandler.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/handler/TelldusDevicesHandler.java @@ -23,12 +23,16 @@ import org.openhab.binding.tellstick.internal.live.xml.DataTypeValue; import org.openhab.binding.tellstick.internal.live.xml.TellstickNetSensor; import org.openhab.binding.tellstick.internal.live.xml.TellstickNetSensorEvent; +import org.openhab.binding.tellstick.internal.local.dto.LocalDataTypeValueDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorEventDTO; import org.openhab.core.config.core.Configuration; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -109,9 +113,15 @@ public void handleCommand(ChannelUID channelUID, Command command) { return; } if (command instanceof RefreshType) { - getBridge().getHandler().handleCommand(channelUID, command); - refreshDevice(dev); - return; + Bridge bridge = getBridge(); + if (bridge != null) { + TelldusBridgeHandler localBridgeHandler = (TelldusBridgeHandler) bridge.getHandler(); + if (localBridgeHandler != null) { + localBridgeHandler.handleCommand(channelUID, command); + refreshDevice(dev); + return; + } + } } if (channelUID.getId().equals(CHANNEL_DIMMER) || channelUID.getId().equals(CHANNEL_STATE)) { try { @@ -123,9 +133,6 @@ public void handleCommand(ChannelUID channelUID, Command command) { } catch (TellstickException e) { logger.debug("Failed to send command to tellstick", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (Exception e) { - logger.error("Failed to send command to tellstick", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } else { logger.warn("Setting of channel {} not possible. Read-only", channelUID); @@ -159,8 +166,9 @@ public void initialize() { if (repeatCount != null) { resend = repeatCount.intValue(); } - if (getBridge() != null) { - bridgeStatusChanged(getBridge().getStatusInfo()); + Bridge bridge = getBridge(); + if (bridge != null) { + bridgeStatusChanged(bridge.getStatusInfo()); } } @@ -169,31 +177,34 @@ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { logger.debug("device: {} bridgeStatusChanged: {}", deviceId, bridgeStatusInfo); if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) { try { - TelldusBridgeHandler tellHandler = (TelldusBridgeHandler) getBridge().getHandler(); - logger.debug("Init bridge for {}, bridge:{}", deviceId, tellHandler); - if (tellHandler != null) { - this.bridgeHandler = tellHandler; - this.bridgeHandler.registerDeviceStatusListener(this); - Configuration config = editConfiguration(); - Device dev = getDevice(tellHandler, deviceId); - if (dev != null) { - if (dev.getName() != null) { - config.put(TellstickBindingConstants.DEVICE_NAME, dev.getName()); - } - if (dev.getProtocol() != null) { - config.put(TellstickBindingConstants.DEVICE_PROTOCOL, dev.getProtocol()); - } - if (dev.getModel() != null) { - config.put(TellstickBindingConstants.DEVICE_MODEL, dev.getModel()); - } - updateConfiguration(config); + Bridge localBridge = getBridge(); + if (localBridge != null) { + TelldusBridgeHandler telldusBridgeHandler = (TelldusBridgeHandler) localBridge.getHandler(); + logger.debug("Init bridge for {}, bridge:{}", deviceId, telldusBridgeHandler); + if (telldusBridgeHandler != null) { + this.bridgeHandler = telldusBridgeHandler; + this.bridgeHandler.registerDeviceStatusListener(this); + Configuration config = editConfiguration(); + Device dev = getDevice(telldusBridgeHandler, deviceId); + if (dev != null) { + if (dev.getName() != null) { + config.put(TellstickBindingConstants.DEVICE_NAME, dev.getName()); + } + if (dev.getProtocol() != null) { + config.put(TellstickBindingConstants.DEVICE_PROTOCOL, dev.getProtocol()); + } + if (dev.getModel() != null) { + config.put(TellstickBindingConstants.DEVICE_MODEL, dev.getModel()); + } + updateConfiguration(config); - updateStatus(ThingStatus.ONLINE); - } else { - logger.warn( - "Could not find {}, please make sure it is defined and that telldus service is running", - deviceId); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + updateStatus(ThingStatus.ONLINE); + } else { + logger.warn( + "Could not find {}, please make sure it is defined and that telldus service is running", + deviceId); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + } } } } catch (Exception e) { @@ -240,6 +251,10 @@ private void updateSensorStates(Device dev) { for (DataTypeValue type : ((TellstickNetSensor) dev).getData()) { updateSensorDataState(type); } + } else if (dev instanceof TellstickLocalSensorDTO) { + for (LocalDataTypeValueDTO type : ((TellstickLocalSensorDTO) dev).getData()) { + updateSensorDataState(type); + } } } @@ -260,6 +275,9 @@ public void onDeviceStateChanged(Bridge bridge, Device device, TellstickEvent ev } else if (event instanceof TellstickNetSensorEvent) { TellstickNetSensorEvent sensorevent = (TellstickNetSensorEvent) event; updateSensorDataState(sensorevent.getDataTypeValue()); + } else if (event instanceof TellstickLocalSensorEventDTO) { + TellstickLocalSensorEventDTO sensorevent = (TellstickLocalSensorEventDTO) event; + updateSensorDataState(sensorevent.getDataTypeValue()); } else if (event instanceof TellstickSensorEvent) { TellstickSensorEvent sensorevent = (TellstickSensorEvent) event; updateSensorDataState(sensorevent.getDataType(), sensorevent.getData()); @@ -340,6 +358,46 @@ private void updateSensorDataState(DataTypeValue dataType) { } } + private void updateSensorDataState(LocalDataTypeValueDTO dataType) { + switch (dataType.getName()) { + case HUMIDITY: + updateState(humidityChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), HUMIDITY_UNIT)); + break; + case TEMPERATURE: + updateState(tempChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), SIUnits.CELSIUS)); + break; + case RAINRATE: + updateState(rainRateChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), RAIN_UNIT)); + break; + case RAINTOTAL: + updateState(raintTotChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), RAIN_UNIT)); + break; + case WINDAVERAGE: + updateState(windAverageChannel, + new QuantityType<>(new BigDecimal(dataType.getValue()), WIND_SPEED_UNIT_MS)); + break; + case WINDDIRECTION: + updateState(windDirectionChannel, + new QuantityType<>(new BigDecimal(dataType.getValue()), WIND_DIRECTION_UNIT)); + break; + case WINDGUST: + updateState(windGuestChannel, + new QuantityType<>(new BigDecimal(dataType.getValue()), WIND_SPEED_UNIT_MS)); + break; + case WATT: + if (dataType.getScale() == 5) { + updateState(ampereChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), ELECTRIC_UNIT)); + } else if (dataType.getScale() == 2) { + updateState(wattChannel, new QuantityType<>(new BigDecimal(dataType.getValue()), Units.WATT)); + } + break; + case LUMINATION: + updateState(luxChannel, new QuantityType<>(new DecimalType(dataType.getValue()), LUX_UNIT)); + break; + default: + } + } + private void updateDeviceState(Device device) { if (device != null) { logger.debug("Updating state of {} {} ({}) id: {}", device.getDeviceType(), device.getName(), diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/live/xml/LiveDataType.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/live/xml/LiveDataType.java index 67dd3f95c91c8..9340acf11340b 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/live/xml/LiveDataType.java +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/live/xml/LiveDataType.java @@ -20,11 +20,11 @@ public enum LiveDataType { HUMIDITY("humidity"), TEMPERATURE("temp"), - WINDAVERAGE("windaverage"), - WINDDIRECTION("winddirection"), - WINDGUST("windgust"), - RAINRATE("rainrate"), - RAINTOTAL("rainttotal"), + WINDAVERAGE("wavg"), + WINDDIRECTION("wdir"), + WINDGUST("wgust"), + RAINRATE("rrate"), + RAINTOTAL("rtot"), WATT("watt"), LUMINATION("lum"), UNKOWN("unkown"); diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalBridgeHandler.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalBridgeHandler.java new file mode 100644 index 0000000000000..d1a7dd4a09b1f --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalBridgeHandler.java @@ -0,0 +1,290 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.tellstick.internal.conf.TelldusLocalConfiguration; +import org.openhab.binding.tellstick.internal.handler.DeviceStatusListener; +import org.openhab.binding.tellstick.internal.handler.TelldusBridgeHandler; +import org.openhab.binding.tellstick.internal.handler.TelldusDeviceController; +import org.openhab.binding.tellstick.internal.handler.TelldusDevicesHandler; +import org.openhab.binding.tellstick.internal.local.dto.LocalDataTypeValueDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDeviceDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDevicesDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorEventDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalSensorsDTO; +import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseBridgeHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tellstick.device.TellstickDeviceEvent; +import org.tellstick.device.TellstickException; +import org.tellstick.device.iface.Device; + +/** + * {@link TelldusLocalBridgeHandler} is the handler for Telldus Local API (Tellstick ZNET v1/v2) and connects it + * to the framework. All {@link TelldusDevicesHandler}s use the + * {@link TelldusLocalDeviceController} to execute the actual commands. + * + * @author Jan Gustafsson- Initial contribution + */ +public class TelldusLocalBridgeHandler extends BaseBridgeHandler implements TelldusBridgeHandler { + + private final Logger logger = LoggerFactory.getLogger(TelldusLocalBridgeHandler.class); + + private TellstickLocalDevicesDTO deviceList = null; + private TellstickLocalSensorsDTO sensorList = null; + private TelldusLocalDeviceController controller = null; + private List deviceStatusListeners = Collections.synchronizedList(new ArrayList<>()); + private final HttpClient httpClient; + private ScheduledFuture pollingJob; + /** + * Use cache for refresh command to not update again when call is made within 10 seconds of previous call. + */ + private final ExpiringCache refreshCache = new ExpiringCache<>(Duration.ofSeconds(10), + this::refreshDeviceList); + + public TelldusLocalBridgeHandler(Bridge bridge, HttpClient httpClient) { + super(bridge); + this.httpClient = httpClient; + } + + @Override + public void initialize() { + TelldusLocalConfiguration configuration = getConfigAs(TelldusLocalConfiguration.class); + this.controller = new TelldusLocalDeviceController(configuration, httpClient); + pollingJob = scheduler.scheduleWithFixedDelay(this::refreshDeviceList, 11, configuration.refreshInterval, + TimeUnit.MILLISECONDS); + updateStatus(ThingStatus.UNKNOWN); + } + + @Override + public void dispose() { + if (pollingJob != null) { + pollingJob.cancel(true); + } + if (this.controller != null) { + this.controller.dispose(); + } + deviceList = null; + sensorList = null; + super.dispose(); + } + + private boolean refreshDeviceList() { + try { + updateDevices(deviceList); + updateSensors(sensorList); + updateStatus(ThingStatus.ONLINE); + return true; + } catch (TellstickException | InterruptedException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + return false; + } + + private synchronized void updateDevices(TellstickLocalDevicesDTO previouslist) + throws TellstickException, InterruptedException { + TellstickLocalDevicesDTO newList = controller + .callRestMethod(TelldusLocalDeviceController.HTTP_LOCAL_API_DEVICES, TellstickLocalDevicesDTO.class); + logger.debug("Device list {}", newList.getDevices()); + if (newList.getDevices() != null) { + if (previouslist == null) { + for (TellstickLocalDeviceDTO device : newList.getDevices()) { + device.setUpdated(true); + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + listener.onDeviceAdded(getThing(), device); + } + } + } + this.deviceList = newList; + } else { + for (TellstickLocalDeviceDTO device : newList.getDevices()) { + int index = previouslist.getDevices().indexOf(device); + logger.debug("Device:{} found at {}", device, index); + if (index >= 0) { + TellstickLocalDeviceDTO orgDevice = previouslist.getDevices().get(index); + if (device.getState() != orgDevice.getState()) { + orgDevice.setState(device.getState()); + orgDevice.setStatevalue(device.getStatevalue()); + orgDevice.setUpdated(true); + } + } else { + logger.debug("New Device - Adding:{}", device); + previouslist.getDevices().add(device); + device.setUpdated(true); + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + listener.onDeviceAdded(getThing(), device); + } + } + } + } + } + + for (TellstickLocalDeviceDTO device : deviceList.getDevices()) { + if (device.isUpdated()) { + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + listener.onDeviceStateChanged(getThing(), device, + new TellstickDeviceEvent(device, null, null, null, System.currentTimeMillis())); + } + } + device.setUpdated(false); + } + } + } + } + + private synchronized void updateSensors(TellstickLocalSensorsDTO previouslist) + throws TellstickException, InterruptedException { + TellstickLocalSensorsDTO newList = controller + .callRestMethod(TelldusLocalDeviceController.HTTP_LOCAL_API_SENSORS, TellstickLocalSensorsDTO.class); + logger.debug("Updated sensors:{}", newList.getSensors()); + if (newList.getSensors() != null) { + if (previouslist == null) { + this.sensorList = newList; + for (TellstickLocalSensorDTO sensor : sensorList.getSensors()) { + sensor.setUpdated(true); + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + listener.onDeviceAdded(getThing(), sensor); + } + } + } + } else { + for (TellstickLocalSensorDTO sensor : previouslist.getSensors()) { + sensor.setUpdated(false); + } + + for (TellstickLocalSensorDTO sensor : newList.getSensors()) { + int index = this.sensorList.getSensors().indexOf(sensor); + if (index >= 0) { + TellstickLocalSensorDTO orgSensor = this.sensorList.getSensors().get(index); + orgSensor.setData(sensor.getData()); + orgSensor.setUpdated(true); + sensor.setUpdated(true); + } else { + this.sensorList.getSensors().add(sensor); + sensor.setUpdated(true); + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + listener.onDeviceAdded(getThing(), sensor); + } + } + } + } + } + for (TellstickLocalSensorDTO sensor : sensorList.getSensors()) { + if (sensor.getData() != null && sensor.isUpdated()) { + synchronized (deviceStatusListeners) { + for (DeviceStatusListener listener : deviceStatusListeners) { + for (LocalDataTypeValueDTO type : sensor.getData()) { + listener.onDeviceStateChanged(getThing(), sensor, + new TellstickLocalSensorEventDTO(sensor.getId(), type.getValue(), type, + sensor.getProtocol(), sensor.getModel(), System.currentTimeMillis())); + } + } + } + sensor.setUpdated(false); + } + } + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + refreshCache.getValue(); + } + } + + @Override + public boolean registerDeviceStatusListener(DeviceStatusListener deviceStatusListener) { + if (deviceStatusListener == null) { + throw new IllegalArgumentException("It's not allowed to pass a null deviceStatusListener."); + } + return deviceStatusListeners.add(deviceStatusListener); + } + + @Override + public boolean unregisterDeviceStatusListener(DeviceStatusListener deviceStatusListener) { + return deviceStatusListeners.remove(deviceStatusListener); + } + + private Device getDevice(String id, List devices) { + for (Device device : devices) { + if (device.getId() == Integer.valueOf(id)) { + return device; + } + } + return null; + } + + private Device getSensor(String id, List sensors) { + for (Device sensor : sensors) { + if (sensor.getId() == Integer.valueOf(id)) { + return sensor; + } + } + return null; + } + + @Override + public Device getDevice(String serialNumber) { + return getDevice(serialNumber, getDevices()); + } + + private List getDevices() { + if (deviceList == null) { + refreshDeviceList(); + } + return deviceList.getDevices(); + } + + @Override + public Device getSensor(String deviceUUId) { + Device result = null; + if (sensorList != null) { + result = getSensor(deviceUUId, sensorList.getSensors()); + } + return result; + } + + @Override + public void rescanTelldusDevices() { + this.deviceList = null; + this.sensorList = null; + refreshDeviceList(); + } + + @Override + public TelldusDeviceController getController() { + return controller; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalDeviceController.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalDeviceController.java new file mode 100644 index 0000000000000..425f76e29ae26 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalDeviceController.java @@ -0,0 +1,282 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local; + +import java.math.BigDecimal; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpMethod; +import org.openhab.binding.tellstick.internal.TelldusBindingException; +import org.openhab.binding.tellstick.internal.conf.TelldusLocalConfiguration; +import org.openhab.binding.tellstick.internal.handler.TelldusDeviceController; +import org.openhab.binding.tellstick.internal.local.dto.TelldusLocalResponseDTO; +import org.openhab.binding.tellstick.internal.local.dto.TellstickLocalDeviceDTO; +import org.openhab.core.library.types.IncreaseDecreaseType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.PercentType; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.tellstick.JNA; +import org.tellstick.device.TellstickDevice; +import org.tellstick.device.TellstickDeviceEvent; +import org.tellstick.device.TellstickException; +import org.tellstick.device.TellstickSensorEvent; +import org.tellstick.device.iface.Device; +import org.tellstick.device.iface.DeviceChangeListener; +import org.tellstick.device.iface.SensorListener; +import org.tellstick.device.iface.SwitchableDevice; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; + +/** + * {@link TelldusLocalDeviceController} handles the communication with Telldus Local API (Tellstick ZNET v1/v2) + * This controller uses JSON based Rest API to communicate with Telldus Local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TelldusLocalDeviceController implements DeviceChangeListener, SensorListener, TelldusDeviceController { + private final Logger logger = LoggerFactory.getLogger(TelldusLocalDeviceController.class); + private long lastSend = 0; + public static final long DEFAULT_INTERVAL_BETWEEN_SEND_SEC = 250; + static final int REQUEST_TIMEOUT_MS = 5000; + private final HttpClient httpClient; + private final Gson gson = new Gson(); + private String localApiUrl; + private String authorizationHeader = "Bearer "; + static final String HTTP_LOCAL_API = "api/"; + static final String HTTP_LOCAL_API_DEVICES = HTTP_LOCAL_API + "devices/list?supportedMethods=19&includeIgnored=0"; + static final String HTTP_LOCAL_API_SENSORS = HTTP_LOCAL_API + + "sensors/list?includeValues=1&includeScale=1&includeUnit=1&includeIgnored=0"; + static final String HTTP_LOCAL_API_SENSOR_INFO = HTTP_LOCAL_API + "sensor/info"; + static final String HTTP_LOCAL_API_DEVICE_DIM = HTTP_LOCAL_API + "device/dim?id=%d&level=%d"; + static final String HTTP_LOCAL_API_DEVICE_TURNOFF = HTTP_LOCAL_API + "device/turnOff?id=%d"; + static final String HTTP_LOCAL_DEVICE_TURNON = HTTP_LOCAL_API + "device/turnOn?id=%d"; + private static final int MAX_RETRIES = 3; + + public TelldusLocalDeviceController(TelldusLocalConfiguration configuration, HttpClient httpClient) { + this.httpClient = httpClient; + localApiUrl = "http://" + configuration.ipAddress + "/"; + authorizationHeader = authorizationHeader + configuration.accessToken; + } + + @Override + public void dispose() { + } + + @Override + public void handleSendEvent(Device device, int resendCount, boolean isdimmer, Command command) + throws TellstickException { + logger.debug("Send {} to {}", command, device); + try { + if (device instanceof TellstickLocalDeviceDTO) { + if (command == OnOffType.ON) { + turnOn(device); + } else if (command == OnOffType.OFF) { + turnOff(device); + } else if (command instanceof PercentType) { + dim(device, (PercentType) command); + } else if (command instanceof IncreaseDecreaseType) { + increaseDecrease(device, ((IncreaseDecreaseType) command)); + } + } else if (device instanceof SwitchableDevice) { + if (command == OnOffType.ON) { + if (isdimmer) { + logger.trace("Turn off first in case it is allready on"); + turnOff(device); + } + turnOn(device); + } else if (command == OnOffType.OFF) { + turnOff(device); + } + } else { + logger.warn("Cannot send to {}", device); + } + } catch (InterruptedException e) { + logger.debug("OH is shut-down."); + } + } + + private void increaseDecrease(Device dev, IncreaseDecreaseType increaseDecreaseType) + throws TellstickException, InterruptedException { + String strValue = ((TellstickDevice) dev).getData(); + double value = 0; + if (strValue != null) { + value = Double.valueOf(strValue); + } + int percent = (int) Math.round((value / 255) * 100); + if (IncreaseDecreaseType.INCREASE == increaseDecreaseType) { + percent = Math.min(percent + 10, 100); + } else if (IncreaseDecreaseType.DECREASE == increaseDecreaseType) { + percent = Math.max(percent - 10, 0); + } + dim(dev, new PercentType(percent)); + } + + private void dim(Device dev, PercentType command) throws TellstickException, InterruptedException { + double value = command.doubleValue(); + + // 0 means OFF and 100 means ON + if (value == 0 && dev instanceof TellstickLocalDeviceDTO) { + turnOff(dev); + } else if (value == 100 && dev instanceof TellstickLocalDeviceDTO) { + turnOn(dev); + } else if (dev instanceof TellstickLocalDeviceDTO + && (((TellstickLocalDeviceDTO) dev).getMethods() & JNA.CLibrary.TELLSTICK_DIM) > 0) { + long tdVal = Math.round((value / 100) * 255); + TelldusLocalResponseDTO response = callRestMethod( + String.format(HTTP_LOCAL_API_DEVICE_DIM, dev.getId(), tdVal), TelldusLocalResponseDTO.class); + handleResponse((TellstickLocalDeviceDTO) dev, response); + } else { + throw new TelldusBindingException("Cannot send DIM to " + dev); + } + } + + private void turnOff(Device dev) throws TellstickException, InterruptedException { + if (dev instanceof TellstickLocalDeviceDTO) { + TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_API_DEVICE_TURNOFF, dev.getId()), + TelldusLocalResponseDTO.class); + handleResponse((TellstickLocalDeviceDTO) dev, response); + } else { + throw new TelldusBindingException("Cannot send OFF to " + dev); + } + } + + private void handleResponse(TellstickLocalDeviceDTO device, TelldusLocalResponseDTO response) + throws TellstickException { + if (response == null || (response.getStatus() == null && response.getError() == null)) { + throw new TelldusBindingException("No response " + response); + } else if (response.getError() != null) { + device.setUpdated(true); + throw new TelldusBindingException("Error " + response.getError()); + } else if (!response.getStatus().trim().equals("success")) { + throw new TelldusBindingException("Response " + response.getStatus()); + } + } + + private void turnOn(Device dev) throws TellstickException, InterruptedException { + if (dev instanceof TellstickLocalDeviceDTO) { + TelldusLocalResponseDTO response = callRestMethod(String.format(HTTP_LOCAL_DEVICE_TURNON, dev.getId()), + TelldusLocalResponseDTO.class); + handleResponse((TellstickLocalDeviceDTO) dev, response); + } else { + throw new TelldusBindingException("Cannot send ON to " + dev); + } + } + + @Override + public State calcState(Device dev) { + TellstickLocalDeviceDTO device = (TellstickLocalDeviceDTO) dev; + State st = null; + + switch (device.getState()) { + case JNA.CLibrary.TELLSTICK_TURNON: + st = OnOffType.ON; + break; + case JNA.CLibrary.TELLSTICK_TURNOFF: + st = OnOffType.OFF; + break; + case JNA.CLibrary.TELLSTICK_DIM: + BigDecimal dimValue = new BigDecimal(device.getStatevalue()); + if (dimValue.intValue() == 0) { + st = OnOffType.OFF; + } else if (dimValue.intValue() >= 255) { + st = OnOffType.ON; + } else { + st = OnOffType.ON; + } + break; + default: + logger.warn("Could not handle {} for {}", device.getState(), device); + } + + return st; + } + + @Override + public BigDecimal calcDimValue(Device device) { + BigDecimal dimValue = BigDecimal.ZERO; + switch (((TellstickLocalDeviceDTO) device).getState()) { + case JNA.CLibrary.TELLSTICK_TURNON: + dimValue = new BigDecimal(100); + break; + case JNA.CLibrary.TELLSTICK_TURNOFF: + break; + case JNA.CLibrary.TELLSTICK_DIM: + dimValue = new BigDecimal(((TellstickLocalDeviceDTO) device).getStatevalue()); + dimValue = dimValue.multiply(new BigDecimal(100)); + dimValue = dimValue.divide(new BigDecimal(255), 0, BigDecimal.ROUND_HALF_UP); + break; + default: + logger.warn("Could not handle {} for {}", (((TellstickLocalDeviceDTO) device).getState()), device); + } + return dimValue; + } + + public long getLastSend() { + return lastSend; + } + + public void setLastSend(long currentTimeMillis) { + lastSend = currentTimeMillis; + } + + @Override + public void onRequest(TellstickSensorEvent newDevices) { + setLastSend(newDevices.getTimestamp()); + } + + @Override + public void onRequest(TellstickDeviceEvent newDevices) { + setLastSend(newDevices.getTimestamp()); + } + + T callRestMethod(String uri, Class response) throws TelldusLocalException, InterruptedException { + T resultObj = null; + try { + for (int i = 0; i < MAX_RETRIES; i++) { + try { + resultObj = innerCallRest(localApiUrl + uri, response); + break; + } catch (TimeoutException e) { + logger.warn("TimeoutException error in get"); + } + } + } catch (JsonSyntaxException e) { + throw new TelldusLocalException(e); + } catch (ExecutionException e) { + throw new TelldusLocalException(e); + } + return resultObj; + } + + private T innerCallRest(String uri, Class json) + throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException { + logger.trace("HTTP GET: {}", uri); + + Request request = httpClient.newRequest(uri).method(HttpMethod.GET); + request.header("Authorization", authorizationHeader); + + ContentResponse response = request.send(); + String content = response.getContentAsString(); + logger.trace("API response: {}", content); + + return gson.fromJson(content, json); + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalException.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalException.java new file mode 100644 index 0000000000000..f9da540621220 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/TelldusLocalException.java @@ -0,0 +1,46 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.tellstick.device.TellstickException; + +/** + * {@link TelldusLocalException} is used when there is exception communicating with Telldus local API. + * This exception extends the Telldus Core exception. + * + * @author Jan Gustafsson - Initial contribution + */ +@NonNullByDefault +public class TelldusLocalException extends TellstickException { + + public TelldusLocalException(Exception source) { + super(null, 0); + this.initCause(source); + } + + private static final long serialVersionUID = 3067179547449454711L; + + @Override + public @NonNull String getMessage() { + Throwable throwable = getCause(); + if (throwable != null) { + String localMessage = throwable.getMessage(); + if (localMessage != null) { + return localMessage; + } + } + return ""; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/LocalDataTypeValueDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/LocalDataTypeValueDTO.java new file mode 100644 index 0000000000000..63bab55c26e31 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/LocalDataTypeValueDTO.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import org.openhab.binding.tellstick.internal.live.xml.LiveDataType; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class LocalDataTypeValueDTO { + + private String name; + private int scale; + private String value; + + public LiveDataType getName() { + return LiveDataType.fromName(name); + } + + public void setName(String name) { + this.name = name; + } + + public int getScale() { + return scale; + } + + public void setScale(int scale) { + this.scale = scale; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TelldusLocalResponseDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TelldusLocalResponseDTO.java new file mode 100644 index 0000000000000..9f6d7726bdebe --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TelldusLocalResponseDTO.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TelldusLocalResponseDTO { + + private String error; + private String status; + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDeviceDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDeviceDTO.java new file mode 100644 index 0000000000000..7289dcd999478 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDeviceDTO.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import org.tellstick.device.iface.Device; +import org.tellstick.enums.DeviceType; + +import com.google.gson.annotations.SerializedName; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TellstickLocalDeviceDTO implements Device { + + @SerializedName("id") + private int deviceId; + private int methods; + private String name; + private int state; + private String statevalue; + private String type; + private String protocol; + private String model; + private boolean updated; + + public void setUpdated(boolean b) { + this.updated = b; + } + + public boolean isUpdated() { + return updated; + } + + @Override + public int getId() { + return deviceId; + } + + public void setId(int deviceId) { + this.deviceId = deviceId; + } + + public int getMethods() { + return methods; + } + + public void setMethods(int methods) { + this.methods = methods; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getUUId() { + return Integer.toString(deviceId); + } + + @Override + public String getProtocol() { + return protocol; + } + + @Override + public String getModel() { + return model; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.DEVICE; + } + + public int getState() { + return state; + } + + public void setState(int state) { + this.state = state; + } + + public String getStatevalue() { + return statevalue; + } + + public void setStatevalue(String statevalue) { + this.statevalue = statevalue; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDevicesDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDevicesDTO.java new file mode 100644 index 0000000000000..275768de1388f --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalDevicesDTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TellstickLocalDevicesDTO { + + @SerializedName("device") + private List devices = null; + + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorDTO.java new file mode 100644 index 0000000000000..f8087fb928877 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorDTO.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import java.util.List; + +import org.openhab.binding.tellstick.internal.live.xml.LiveDataType; +import org.tellstick.device.iface.Device; +import org.tellstick.enums.DeviceType; + +import com.google.gson.annotations.SerializedName; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TellstickLocalSensorDTO implements Device { + + private int battery; + private boolean updated; + private List data = null; + @SerializedName("id") + private int deviceId; + private String model; + private String name; + private String protocol; + private int sensorId; + + public int getBattery() { + return battery; + } + + public void setBattery(int battery) { + this.battery = battery; + } + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + + @Override + public int getId() { + return deviceId; + } + + public void setId(int id) { + this.deviceId = id; + } + + @Override + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setUpdated(boolean b) { + this.updated = b; + } + + public boolean isUpdated() { + return updated; + } + + public boolean isSensorOfType(LiveDataType type) { + boolean res = false; + if (data != null) { + for (LocalDataTypeValueDTO val : data) { + if (val.getName() == type) { + res = true; + break; + } + } + } + return res; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.SENSOR; + } + + public int getSensorId() { + return sensorId; + } + + public void setSensorId(int sensorId) { + this.sensorId = sensorId; + } + + @Override + public String getUUId() { + return Integer.toString(deviceId); + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorEventDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorEventDTO.java new file mode 100644 index 0000000000000..39e6744e9e034 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorEventDTO.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import org.openhab.binding.tellstick.internal.TellstickRuntimeException; +import org.tellstick.device.TellstickSensorEvent; +import org.tellstick.device.iface.TellstickEvent; +import org.tellstick.enums.DataType; + +/** + * This class is used for events for the telldus live sensors. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TellstickLocalSensorEventDTO extends TellstickSensorEvent implements TellstickEvent { + + private LocalDataTypeValueDTO dataType; + + public TellstickLocalSensorEventDTO(int sensorId, String data, LocalDataTypeValueDTO dataValue, String protocol, + String model, long timeStamp) { + super(sensorId, data, null, protocol, model, timeStamp); + this.dataType = dataValue; + } + + public LocalDataTypeValueDTO getDataTypeValue() { + return dataType; + } + + @Override + public DataType getDataType() { + throw new TellstickRuntimeException("Should not call this method"); + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorsDTO.java b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorsDTO.java new file mode 100644 index 0000000000000..ff6e64f9607e7 --- /dev/null +++ b/bundles/org.openhab.binding.tellstick/src/main/java/org/openhab/binding/tellstick/internal/local/dto/TellstickLocalSensorsDTO.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2021 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.tellstick.internal.local.dto; + +import java.util.List; + +import com.google.gson.annotations.SerializedName; + +/** + * Class used to deserialize JSON from Telldus local API. + * + * @author Jan Gustafsson - Initial contribution + */ +public class TellstickLocalSensorsDTO { + + @SerializedName("sensor") + private List sensors = null; + + public List getSensors() { + return sensors; + } + + public void setSensors(List sensors) { + this.sensors = sensors; + } +} diff --git a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/bridge.xml index da1ca16a0d627..11f8e2b091fc7 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/bridge.xml @@ -6,7 +6,7 @@ - This bridge represents the telldus center on a local computer. + This bridge represents the Telldus center on a local computer. @@ -25,7 +25,7 @@ - This bridge represents the telldus live cloud service. + This bridge represents the Telldus live cloud service. @@ -34,12 +34,10 @@ The private key from telldus - credentials The public key from telldus - credentials The openauth token. @@ -48,7 +46,7 @@ The openauth token secret. - + The refresh interval in ms which is used to poll Telldus Live. @@ -57,4 +55,27 @@ + + + This bridge represents the Telldus local API. + + + + + The local IP address of the Tellstick. + network-address + + + + The access token. + + + + The refresh interval in ms which is used to poll Telldus local API. + + 60000 + + + + diff --git a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/devices.xml b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/devices.xml index 3e2fa9f1bc596..f51e8f2d9d7f3 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/devices.xml +++ b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/devices.xml @@ -9,6 +9,7 @@ + diff --git a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/sensor.xml b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/sensor.xml index 4a598a43244ec..5e1820371b896 100644 --- a/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/sensor.xml +++ b/bundles/org.openhab.binding.tellstick/src/main/resources/OH-INF/thing/sensor.xml @@ -8,6 +8,7 @@ + @@ -91,14 +92,14 @@ Number:Length The current rain rate - + Number:Length Total rain - + @@ -126,9 +127,9 @@ Number:Power - - Current kWatt - + + Current power + From 0082cd11aeb19ff560f0292fdcfe2a917508af22 Mon Sep 17 00:00:00 2001 From: leoguiders <30723929+leoguiders@users.noreply.github.com> Date: Thu, 18 Feb 2021 14:07:31 +0100 Subject: [PATCH 013/118] [modbus.sunspec] Fix decimal number handling for inverter channel types (#10195) Signed-off-by: Jan Philipp Giel --- .../main/resources/OH-INF/thing/inverter-channel-types.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/OH-INF/thing/inverter-channel-types.xml b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/OH-INF/thing/inverter-channel-types.xml index fa0506bbc02a8..0ee3ccb91824a 100644 --- a/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/OH-INF/thing/inverter-channel-types.xml +++ b/bundles/org.openhab.binding.modbus.sunspec/src/main/resources/OH-INF/thing/inverter-channel-types.xml @@ -20,14 +20,14 @@ Number:ElectricPotential This phase's AC voltage relative to the next phase - + Number:ElectricPotential This phase's AC voltage relative to N line - + From 1c5f0d17971b7f2e83e397370c263e347762a992 Mon Sep 17 00:00:00 2001 From: Pantastisch <45369229+Pantastisch@users.noreply.github.com> Date: Fri, 19 Feb 2021 14:35:23 +0100 Subject: [PATCH 014/118] [icloud] Add german translation (#10164) Signed-off-by: Panagiotis Doulgeris panagiotis.doulgeris@outlook.com Signed-off-by: Pantastisch <45369229+Pantastisch@users.noreply.github.com> --- .../OH-INF/i18n/iCloud_de.properties | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bundles/org.openhab.binding.icloud/src/main/resources/OH-INF/i18n/iCloud_de.properties diff --git a/bundles/org.openhab.binding.icloud/src/main/resources/OH-INF/i18n/iCloud_de.properties b/bundles/org.openhab.binding.icloud/src/main/resources/OH-INF/i18n/iCloud_de.properties new file mode 100644 index 0000000000000..2b6beab311a3f --- /dev/null +++ b/bundles/org.openhab.binding.icloud/src/main/resources/OH-INF/i18n/iCloud_de.properties @@ -0,0 +1,34 @@ +# Binding +icloud.binding.name=iCloud Binding +icloud.binding.description=Die Apple iCloud wird genutzt, um Daten wie den Ladezustand oder den Standort von einem oder mehreren Apple Geräten zu erhalten, die mit einem iCloud Account verknüpft sind. + +# Account Thing +icloud.account-thing.label=iCloud Account +icloud.account-thing.description=Der iCloud Account (Bridge) repräsentiert einen iCloud Account. Du benötigst mehrere iCloud Account Bridges, um mehrere iCloud Accounts zu verwalten. + +icloud.account-thing.parameter.apple-id.label=Apple-ID +icloud.account-thing.parameter.apple-id.description=Apple-ID (E-Mail Adresse), um Zugriff zur iCloud zu erhalten. +icloud.account-thing.parameter.password.label=Passwort +icloud.account-thing.parameter.password.description=Passwort der Apple-ID, um Zugriff zur iCloud zu erhalten. +icloud.account-thing.parameter.refresh.label=Aktualisierungszeit in Minuten +icloud.account-thing.parameter.refresh.description=Zeit in der die iCloud Informationen aktualisiert werden sollen. + +icloud.account-thing.property.owner=Besitzer + +# Device Thing +icloud.device-thing.label=iCloud Gerät +icloud.device-thing.description=Das iCloud Gerät (Thing) repräsentiert ein mit der iCloud verknüpftes Apple Gerät, wie zum Beispiel ein iPhone. Es muss mit einem iCloud Account (Bridge) verknüpft werden, um Aktualisierungen zu erhalten. Mehrere iCloud Geräte können mit einem iCloud Account (Bridge) verknüpft werden. + +icloud.device-thing.parameter.id.label=Geräte-ID + +icloud.device-thing.channel.battery-status.label=Ladezustand +icloud.device-thing.channel.battery-status.state.not-charging=Lädt nicht +icloud.device-thing.channel.battery-status.state.charged=Aufgeladen +icloud.device-thing.channel.battery-status.state.charging=Lädt +icloud.device-thing.channel.battery-status.state.unknown=Unbekannt +icloud.device-thing.channel.find-my-phone.label=Wo ist? +icloud.device-thing.channel.location.label=Standort +icloud.device-thing.channel.location-accuracy=Standort Genauigkeit +icloud.device-thing.channel.location-last-update=Letztes Standort Update + +icloud.device-thing.property.device-name=Gerätename From a9f440dba22d649d3b1e149adfc70d7817c69dbd Mon Sep 17 00:00:00 2001 From: Andrew Fiddian-Green Date: Fri, 19 Feb 2021 19:16:26 +0000 Subject: [PATCH 015/118] [hue] Eliminate NPE in #9985 (#10199) * [hue] extra null check Signed-off-by: Andrew Fiddian-Green --- .../internal/discovery/HueBridgeDiscoveryParticipant.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java index d5bb4ce498354..7018c62917b3a 100644 --- a/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.hue/src/main/java/org/openhab/binding/hue/internal/discovery/HueBridgeDiscoveryParticipant.java @@ -119,9 +119,11 @@ public long getRemovalGracePeriodSeconds(RemoteDevice device) { try { Configuration conf = configAdmin.getConfiguration("binding.hue"); Dictionary properties = conf.getProperties(); - Object property = properties.get(HueBindingConstants.REMOVAL_GRACE_PERIOD); - if (property != null) { - removalGracePeriodSeconds = Long.parseLong(property.toString()); + if (properties != null) { + Object property = properties.get(HueBindingConstants.REMOVAL_GRACE_PERIOD); + if (property != null) { + removalGracePeriodSeconds = Long.parseLong(property.toString()); + } } } catch (IOException | IllegalStateException | NumberFormatException e) { // fall through to pre-initialised (default) value From fd1c96677e37352283875f25755ab510b4f7dffa Mon Sep 17 00:00:00 2001 From: Daniel Weber <25605184+fruggy83@users.noreply.github.com> Date: Sat, 20 Feb 2021 17:13:28 +0100 Subject: [PATCH 016/118] [enocean] Improved device discovery and added SMACK capability (#10157) * Added SMACK teach in * Teached in devices can be teach out on a repeated teach in * Improved detection of RPS devices, device types can be better distinguished now * Bugfixes for discovery fallback to GenericThings * Responses to message requests are send automatically now, no need for linking SEND_COMMAND channel Fixes #10156 Signed-off-by: Daniel Weber --- bundles/org.openhab.binding.enocean/README.md | 19 +- .../internal/EnOceanBindingConstants.java | 24 +-- .../internal/EnOceanHandlerFactory.java | 6 +- .../config/EnOceanActuatorConfig.java | 2 +- .../internal/config/EnOceanBaseConfig.java | 8 + .../internal/config/EnOceanBridgeConfig.java | 8 +- .../EnOceanChannelTransformationConfig.java | 9 +- .../EnOceanDeviceDiscoveryService.java | 169 +++++++++++++----- .../enocean/internal/eep/A5_02/A5_02.java | 4 - .../enocean/internal/eep/A5_04/A5_04.java | 3 - .../enocean/internal/eep/A5_07/A5_07.java | 3 - .../enocean/internal/eep/A5_08/A5_08.java | 3 - .../enocean/internal/eep/A5_10/A5_10.java | 3 - .../enocean/internal/eep/A5_20/A5_20_04.java | 2 +- .../internal/eep/Base/PTM200Message.java | 8 +- .../internal/eep/Base/UTEResponse.java | 5 +- .../Base/_4BSTeachInVariation3Response.java | 6 +- .../internal/eep/Base/_RPSMessage.java | 2 + .../enocean/internal/eep/D2_05/D2_05_00.java | 3 +- .../enocean/internal/eep/EEPFactory.java | 151 ++++++++++++---- .../binding/enocean/internal/eep/EEPType.java | 101 ++++++++--- .../enocean/internal/eep/F6_01/F6_01_01.java | 9 +- .../enocean/internal/eep/F6_02/F6_02_01.java | 26 ++- .../enocean/internal/eep/F6_02/F6_02_02.java | 13 +- .../enocean/internal/eep/F6_05/F6_05_02.java | 9 +- .../enocean/internal/eep/F6_10/F6_10_00.java | 8 +- .../eep/F6_10/F6_10_00_EltakoFPE.java | 6 + .../enocean/internal/eep/F6_10/F6_10_01.java | 9 +- .../internal/eep/Generic/GenericEEP.java | 5 +- .../handler/EnOceanBaseActuatorHandler.java | 73 ++++---- .../handler/EnOceanBaseSensorHandler.java | 20 ++- .../handler/EnOceanBaseThingHandler.java | 17 +- .../handler/EnOceanBridgeHandler.java | 156 ++++++++++++---- .../handler/EnOceanClassicDeviceHandler.java | 2 +- .../internal/messages/ESP3PacketFactory.java | 26 +++ .../internal/messages/EventMessage.java | 65 +++++++ .../enocean/internal/messages/Response.java | 2 +- .../{ => Responses}/BaseResponse.java | 3 +- .../{ => Responses}/RDBaseIdResponse.java | 3 +- .../Responses/RDLearnedClientsResponse.java | 61 +++++++ .../{ => Responses}/RDRepeaterResponse.java | 3 +- .../{ => Responses}/RDVersionResponse.java | 3 +- .../Responses/SMACKTeachInResponse.java | 65 +++++++ .../enocean/internal/messages/SAMessage.java | 64 +++++++ .../transceiver/EnOceanESP3Transceiver.java | 19 +- .../transceiver/EnOceanTransceiver.java | 90 +++++++--- .../internal/transceiver/EventListener.java | 23 +++ .../internal/transceiver/PacketListener.java | 2 +- .../internal/transceiver/TeachInListener.java | 21 +++ .../resources/OH-INF/thing/CentralCommand.xml | 2 +- .../resources/OH-INF/thing/ClassicDevice.xml | 2 +- .../resources/OH-INF/thing/GenericThing.xml | 2 +- .../OH-INF/thing/HeatRecoveryVentilation.xml | 2 +- .../OH-INF/thing/MeasurementSwitch.xml | 2 +- .../resources/OH-INF/thing/Rollershutter.xml | 2 +- .../resources/OH-INF/thing/Thermostat.xml | 2 +- .../main/resources/OH-INF/thing/bridge.xml | 15 +- .../main/resources/OH-INF/thing/channels.xml | 7 - 58 files changed, 1064 insertions(+), 314 deletions(-) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/BaseResponse.java (85%) rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDBaseIdResponse.java (91%) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDRepeaterResponse.java (93%) rename bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/{ => Responses}/RDVersionResponse.java (94%) create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java create mode 100644 bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java diff --git a/bundles/org.openhab.binding.enocean/README.md b/bundles/org.openhab.binding.enocean/README.md index e6ff99b0a1686..38600c86d2e4b 100644 --- a/bundles/org.openhab.binding.enocean/README.md +++ b/bundles/org.openhab.binding.enocean/README.md @@ -126,7 +126,21 @@ The corresponding channels are created dynamically, too. If the actuator supports UTE teach-in, the corresponding thing can be created and paired automatically. First you have to **start the discovery scan for a gateway**. Then press the teach-in button of the actuator. -If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels. +If the EEP of the actuator is known, the binding sends an UTE teach-in response with a new SenderId and creates a new thing with its channels. + +This binding supports so called smart acknowlegde (SMACK) devices too. +Before you can pair a SMACK device you have to configure your gateway bridge as a SMACK postmaster. +If this option is enabled you can pair up to 20 SMACK devices with your gateway. + +Communication between your gateway and a SMACK device is handled through mailboxes. +A mailbox is created for each paired SMACK device and deleted after teach out. +You can see the paired SMACK devices and their mailbox index in the gateway properties. +SMACK devices send periodically status updates followed by a response request. +Whenever such a request is received a `requestAnswer` event is triggered for channel `statusRequestEvent`. +Afterwards you have 100ms time to recalculate your items states and update them. +A message with the updated item states is built, put into the corresponding mailbox and automatically sent upon request of the device. +Pairing and unpairing can be done through a discovery scan. +The corresponding thing of an unpaired device gets disabled, you have to delete it manually if you want to. If the actuator does not support UTE teach-ins, you have to create, configure and choose the right EEP of the thing manually. It is important to link the teach-in channel of this thing to a switch item. @@ -158,6 +172,8 @@ If you change the SenderId of your thing, you have to pair again the thing with | | espVersion | ESP Version of gateway | ESP3, ESP2 | | | rs485 | If gateway is directly connected to a RS485 bus the BaseId is set to 0x00 | true, false | | rs485BaseId | Override BaseId 0x00 if your bus contains a telegram duplicator (FTD14 for ex) | 4 byte hex value | +| | enableSmack | Enables SMACK pairing and handling of SMACK messages | true, false | +| | sendTeachOuts | Defines if a repeated teach in request should be answered with a learned in or teach out response | true, false | | pushButton | receivingEEPId | EEP used for receiving msg | F6_01_01, D2_03_0A | | | enoceanId | EnOceanId of device this thing belongs to | hex value as string | | rockerSwitch | receivingEEPId | | F6_02_01, F6_02_02 | @@ -300,6 +316,7 @@ The channels of a thing are determined automatically based on the chosen EEP. | rssi | Number | Received Signal Strength Indication (dBm) of last received message | | repeatCount | Number | Number of repeaters involved in the transmission of the telegram | | lastReceived | DateTime | Date and time the last telegram was received | +| statusRequestEvent | Trigger | Emits event 'requestAnswer' | Items linked to bi-directional actuators (actuator sends status messages back) should always disable the `autoupdate`. This is especially true for Eltako rollershutter, as their position is calculated out of the current position and the moving time. diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java index 4ae4bf2240bca..c6b3814c5edbc 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanBindingConstants.java @@ -177,7 +177,7 @@ public class EnOceanBindingConstants { public static final String CHANNEL_WAKEUPCYCLE = "wakeUpCycle"; public static final String CHANNEL_SERVICECOMMAND = "serviceCommand"; public static final String CHANNEL_STATUS_REQUEST_EVENT = "statusRequestEvent"; - public static final String CHANNEL_SEND_COMMAND = "sendCommand"; + public static final String VIRTUALCHANNEL_SEND_COMMAND = "sendCommand"; public static final String CHANNEL_VENTILATIONOPERATIONMODE = "ventilationOperationMode"; public static final String CHANNEL_FIREPLACESAFETYMODE = "fireplaceSafetyMode"; @@ -293,7 +293,8 @@ public class EnOceanBindingConstants { Map.entry(CHANNEL_INDOORAIRANALYSIS, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_INDOORAIRANALYSIS), CoreItemFactory.STRING)), - Map.entry(CHANNEL_SETPOINT, + Map.entry( + CHANNEL_SETPOINT, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SETPOINT), CoreItemFactory.NUMBER)), Map.entry(CHANNEL_CONTACT, @@ -444,13 +445,6 @@ public class EnOceanBindingConstants { new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SERVICECOMMAND), CoreItemFactory.NUMBER)), - Map.entry(CHANNEL_STATUS_REQUEST_EVENT, - new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null, - "", false, true)), - Map.entry(CHANNEL_SEND_COMMAND, - new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_SEND_COMMAND), - CoreItemFactory.SWITCH)), - Map.entry(CHANNEL_VENTILATIONOPERATIONMODE, new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_VENTILATIONOPERATIONMODE), CoreItemFactory.STRING)), @@ -527,6 +521,10 @@ public class EnOceanBindingConstants { CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR + Dimensionless.class.getSimpleName())), + Map.entry(CHANNEL_STATUS_REQUEST_EVENT, + new EnOceanChannelDescription(new ChannelTypeUID(BINDING_ID, CHANNEL_STATUS_REQUEST_EVENT), null, + "", false, true)), + Map.entry(CHANNEL_REPEATERMODE, new EnOceanChannelDescription( new ChannelTypeUID(BINDING_ID, CHANNEL_REPEATERMODE), CoreItemFactory.STRING))); @@ -536,11 +534,8 @@ public class EnOceanBindingConstants { public static final String REPEATERMODE_LEVEL_2 = "LEVEL2"; // Bridge config properties - public static final String SENDERID = "senderId"; public static final String PATH = "path"; - public static final String HOST = "host"; - public static final String RS485 = "rs485"; - public static final String NEXTSENDERID = "nextSenderId"; + public static final String PARAMETER_NEXT_SENDERID = "nextSenderId"; // Bridge properties public static final String PROPERTY_BASE_ID = "Base ID"; @@ -551,13 +546,12 @@ public class EnOceanBindingConstants { public static final String PROPERTY_DESCRIPTION = "Description"; // Thing properties - public static final String PROPERTY_ENOCEAN_ID = "enoceanId"; + public static final String PROPERTY_SENDINGENOCEAN_ID = "SendingEnoceanId"; // Thing config parameter public static final String PARAMETER_SENDERIDOFFSET = "senderIdOffset"; public static final String PARAMETER_SENDINGEEPID = "sendingEEPId"; public static final String PARAMETER_RECEIVINGEEPID = "receivingEEPId"; - public static final String PARAMETER_EEPID = "eepId"; public static final String PARAMETER_BROADCASTMESSAGES = "broadcastMessages"; public static final String PARAMETER_ENOCEANID = "enoceanId"; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java index 3ac9ea809cced..bbc82fff8d2b9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/EnOceanHandlerFactory.java @@ -28,6 +28,7 @@ import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingManager; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; @@ -60,6 +61,9 @@ public class EnOceanHandlerFactory extends BaseThingHandlerFactory { @Reference ItemChannelLinkRegistry itemChannelLinkRegistry; + @Reference + ThingManager thingManager; + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -96,7 +100,7 @@ protected void removeHandler(ThingHandler thingHandler) { } private void registerDeviceDiscoveryService(EnOceanBridgeHandler handler) { - EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler); + EnOceanDeviceDiscoveryService discoveryService = new EnOceanDeviceDiscoveryService(handler, thingManager); discoveryService.activate(); this.discoveryServiceRegs.put(handler.getThing().getUID(), bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>())); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java index 461e2e0c95d5d..eb9291abcafca 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanActuatorConfig.java @@ -19,7 +19,7 @@ public class EnOceanActuatorConfig extends EnOceanBaseConfig { public int channel; - public int senderIdOffset = -1; + public Integer senderIdOffset = null; public String manufacturerId; public String teachInType; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java index 49a0cdae330df..100d83a3b8633 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBaseConfig.java @@ -17,15 +17,23 @@ import java.util.ArrayList; import java.util.List; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.util.HexUtils; /** * * @author Daniel Weber - Initial contribution */ +@NonNullByDefault public class EnOceanBaseConfig { + /** + * EnOceanId of the physical device + */ public String enoceanId; + /** + * EEP used/send by physical device + */ public List receivingEEPId = new ArrayList<>(); public boolean receivingSIGEEP = false; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java index baad63a36ed47..6e6bc671a4461 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanBridgeConfig.java @@ -46,10 +46,16 @@ public static ESPVersion getESPVersion(String espVersion) { public boolean rs485; public String rs485BaseId; - public int nextSenderId = 0; + public Integer nextSenderId; + + public boolean enableSmack; + public boolean sendTeachOuts; public EnOceanBridgeConfig() { espVersion = "ESP3"; + sendTeachOuts = false; + enableSmack = true; + nextSenderId = null; } public ESPVersion getESPVersion() { diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java index 36471ec37be02..81c701f4579e4 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/config/EnOceanChannelTransformationConfig.java @@ -12,12 +12,19 @@ */ package org.openhab.binding.enocean.internal.config; +import org.openhab.core.config.core.Configuration; + /** * * @author Daniel Weber - Initial contribution */ -public class EnOceanChannelTransformationConfig { +public class EnOceanChannelTransformationConfig extends Configuration { public String transformationType; public String transformationFunction; + + public EnOceanChannelTransformationConfig() { + put("transformationType", ""); + put("transformationFunction", ""); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java index 030e601e576bf..0373c474ba586 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/discovery/EnOceanDeviceDiscoveryService.java @@ -25,9 +25,14 @@ import org.openhab.binding.enocean.internal.messages.BasePacket; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; -import org.openhab.binding.enocean.internal.transceiver.PacketListener; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; +import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse; +import org.openhab.binding.enocean.internal.transceiver.TeachInListener; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingManager; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; import org.openhab.core.util.HexUtils; @@ -39,15 +44,16 @@ * * @author Daniel Weber - Initial contribution */ - -public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements PacketListener { +public class EnOceanDeviceDiscoveryService extends AbstractDiscoveryService implements TeachInListener { private final Logger logger = LoggerFactory.getLogger(EnOceanDeviceDiscoveryService.class); private EnOceanBridgeHandler bridgeHandler; + private ThingManager thingManager; - public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler) { + public EnOceanDeviceDiscoveryService(EnOceanBridgeHandler bridgeHandler, ThingManager thingManager) { super(null, 60, false); this.bridgeHandler = bridgeHandler; + this.thingManager = thingManager; } /** @@ -102,72 +108,139 @@ public void packetReceived(BasePacket packet) { } String enoceanId = HexUtils.bytesToHex(eep.getSenderId()); - ThingTypeUID thingTypeUID = eep.getThingTypeUID(); - ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId); - - int senderIdOffset = 0; - boolean broadcastMessages = true; - - // check for bidirectional communication => do not use broadcast in this case - if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] - & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) { - broadcastMessages = false; - } - - // if ute => send response if needed - if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) { - logger.info("Sending UTE response to {}", enoceanId); - senderIdOffset = sendTeachInResponse(msg, enoceanId); - } - - // if 4BS teach in variation 3 => send response - if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) { - logger.info("Sending 4BS teach in variation 3 response to {}", enoceanId); - senderIdOffset = sendTeachInResponse(msg, enoceanId); - } - DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID) - .withRepresentationProperty(enoceanId).withBridge(bridgeHandler.getThing().getUID()); + bridgeHandler.getThing().getThings().stream() + .filter(t -> t.getConfiguration().getProperties().getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID) + .toString().equals(enoceanId)) + .findFirst().ifPresentOrElse(t -> { + // If repeated learn is not allowed => send teach out + // otherwise do nothing + if (bridgeHandler.sendTeachOuts()) { + sendTeachOutResponse(msg, enoceanId, t); + thingManager.setEnabled(t.getUID(), false); + } + }, () -> { + Integer senderIdOffset = null; + boolean broadcastMessages = true; + + // check for bidirectional communication => do not use broadcast in this case + if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] + & UTEResponse.CommunicationType_MASK) == UTEResponse.CommunicationType_MASK) { + broadcastMessages = false; + } + + if (msg.getRORG() == RORG.UTE && (msg.getPayload(1, 1)[0] & UTEResponse.ResponseNeeded_MASK) == 0) { + // if ute => send response if needed + logger.debug("Sending UTE response to {}", enoceanId); + senderIdOffset = sendTeachInResponse(msg, enoceanId); + if (senderIdOffset == null) { + return; + } + } else if ((eep instanceof _4BSMessage) && ((_4BSMessage) eep).isTeachInVariation3Supported()) { + // if 4BS teach in variation 3 => send response + logger.debug("Sending 4BS teach in variation 3 response to {}", enoceanId); + senderIdOffset = sendTeachInResponse(msg, enoceanId); + if (senderIdOffset == null) { + return; + } + } + + createDiscoveryResult(eep, broadcastMessages, senderIdOffset); + }); + } - eep.addConfigPropertiesTo(discoveryResultBuilder); - discoveryResultBuilder.withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages); - discoveryResultBuilder.withProperty(PARAMETER_ENOCEANID, enoceanId); + @Override + public void eventReceived(EventMessage event) { + if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) { + EEP eep = EEPFactory.buildEEPFromTeachInSMACKEvent(event); + if (eep == null) { + return; + } - if (senderIdOffset > 0) { - // advance config with new device id - discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset); + SMACKTeachInResponse response = EEPFactory.buildResponseFromSMACKTeachIn(event, + bridgeHandler.sendTeachOuts()); + if (response != null) { + bridgeHandler.sendMessage(response, null); + + if (response.isTeachIn()) { + // SenderIdOffset will be determined during Thing init + createDiscoveryResult(eep, false, -1); + } else if (response.isTeachOut()) { + // disable already teached in thing + bridgeHandler.getThing().getThings().stream() + .filter(t -> t.getConfiguration().getProperties() + .getOrDefault(PARAMETER_ENOCEANID, EMPTYENOCEANID).toString() + .equals(HexUtils.bytesToHex(eep.getSenderId()))) + .findFirst().ifPresentOrElse(t -> { + thingManager.setEnabled(t.getUID(), false); + logger.info("Disable thing with id {}", t.getUID()); + }, () -> { + logger.info("Thing for EnOceanId {} already deleted", + HexUtils.bytesToHex(eep.getSenderId())); + }); + } + } } - - thingDiscovered(discoveryResultBuilder.build()); - - // As we only support sensors to be teached in, we do not need to send a teach in response => 4bs - // bidirectional teach in proc is not supported yet - // this is true except for UTE teach in => we always have to send a response here } - private int sendTeachInResponse(ERP1Message msg, String enoceanId) { - int offset; + private Integer sendTeachInResponse(ERP1Message msg, String enoceanId) { // get new sender Id - offset = bridgeHandler.getNextSenderId(enoceanId); - if (offset > 0) { + Integer offset = bridgeHandler.getNextSenderId(enoceanId); + if (offset != null) { byte[] newSenderId = bridgeHandler.getBaseId(); newSenderId[3] += offset; // send response - EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId); + EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, newSenderId, true); if (response != null) { bridgeHandler.sendMessage(response.getERP1Message(), null); - logger.info("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId, + logger.debug("Teach in response for {} with new senderId {} (= offset {}) sent", enoceanId, HexUtils.bytesToHex(newSenderId), offset); } else { logger.warn("Teach in response for enoceanId {} not supported!", enoceanId); } + } else { + logger.warn("Could not get new SenderIdOffset"); } return offset; } + private void sendTeachOutResponse(ERP1Message msg, String enoceanId, Thing thing) { + byte[] senderId = bridgeHandler.getBaseId(); + senderId[3] += (byte) thing.getConfiguration().getProperties().getOrDefault(PARAMETER_SENDERIDOFFSET, 0); + + // send response + EEP response = EEPFactory.buildResponseEEPFromTeachInERP1(msg, senderId, false); + if (response != null) { + bridgeHandler.sendMessage(response.getERP1Message(), null); + logger.debug("Teach out response for thing {} with EnOceanId {} sent", thing.getUID().getId(), enoceanId); + } else { + logger.warn("Teach out response for enoceanId {} not supported!", enoceanId); + } + } + + protected void createDiscoveryResult(EEP eep, boolean broadcastMessages, Integer senderIdOffset) { + String enoceanId = HexUtils.bytesToHex(eep.getSenderId()); + ThingTypeUID thingTypeUID = eep.getThingTypeUID(); + ThingUID thingUID = new ThingUID(thingTypeUID, bridgeHandler.getThing().getUID(), enoceanId); + + DiscoveryResultBuilder discoveryResultBuilder = DiscoveryResultBuilder.create(thingUID) + .withRepresentationProperty(PARAMETER_ENOCEANID).withProperty(PARAMETER_ENOCEANID, enoceanId) + .withProperty(PARAMETER_BROADCASTMESSAGES, broadcastMessages) + .withBridge(bridgeHandler.getThing().getUID()); + + eep.addConfigPropertiesTo(discoveryResultBuilder); + + if (senderIdOffset != null) { + // advance config with new device id + discoveryResultBuilder.withProperty(PARAMETER_SENDERIDOFFSET, senderIdOffset); + } + + thingDiscovered(discoveryResultBuilder.build()); + } + @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { // we just want teach in msg, so return zero here return 0; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java index 30c8522f7c2d2..957296c15f37f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_02/A5_02.java @@ -20,7 +20,6 @@ import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.types.State; -import org.openhab.core.types.UnDefType; /** * @@ -51,9 +50,6 @@ protected int getUnscaledTemperatureValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } double scaledTemp = getScaledMin() - (((getUnscaledMin() - getUnscaledTemperatureValue()) * (getScaledMin() - getScaledMax())) diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java index fa4241caf9548..355d6365b356e 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_04/A5_04.java @@ -62,9 +62,6 @@ protected int getUnscaledHumidityValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_TEMPERATURE)) { double scaledTemp = getScaledTemperatureMin() diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java index ac78bbf02e832..56f074f799ae9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_07/A5_07.java @@ -53,9 +53,6 @@ protected State getSupplyVoltage(int value) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_ILLUMINATION)) { return getIllumination(); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java index 5f6a54c806dd1..cf471e7fd7136 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_08/A5_08.java @@ -71,9 +71,6 @@ protected int getUnscaledIlluminationValue() { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } if (channelId.equals(CHANNEL_TEMPERATURE)) { double scaledTemp = getScaledTemperatureMin() diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java index 99def10649e15..465ce91d2528f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_10/A5_10.java @@ -40,9 +40,6 @@ public A5_10(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_FANSPEEDSTAGE: diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java index 1c54b86f78c95..058a34b760163 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/A5_20/A5_20_04.java @@ -169,7 +169,7 @@ private byte getSer(Function getCurrentStateFunc) { @Override protected void convertFromCommandImpl(String channelId, String channelTypeId, Command command, Function getCurrentStateFunc, Configuration config) { - if (CHANNEL_SEND_COMMAND.equals(channelId) && (command.equals(OnOffType.ON))) { + if (VIRTUALCHANNEL_SEND_COMMAND.equals(channelId)) { byte db3 = getPos(getCurrentStateFunc); byte db2 = getTsp(getCurrentStateFunc); byte db1 = (byte) (0x00 | getMc(getCurrentStateFunc) | getWuc(getCurrentStateFunc)); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java index ce476f4f85237..ead041877797c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/PTM200Message.java @@ -55,9 +55,6 @@ protected void convertFromCommandImpl(String channelId, String channelTypeId, Co @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_GENERAL_SWITCHING: @@ -77,4 +74,9 @@ protected State convertToStateImpl(String channelId, String channelTypeId, return UnDefType.UNDEF; } + + @Override + public boolean isValidForTeachIn() { + return false; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java index 41cdcb1a88462..21dba840f65b3 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/UTEResponse.java @@ -27,11 +27,12 @@ public class UTEResponse extends _VLDMessage { public static final byte ResponseNeeded_MASK = 0x40; public static final byte TeachIn_NotSpecified = 0x20; - public UTEResponse(ERP1Message packet) { + public UTEResponse(ERP1Message packet, boolean teachIn) { int dataLength = packet.getPayload().length - ESP3_SENDERID_LENGTH - ESP3_RORG_LENGTH - ESP3_STATUS_LENGTH; setData(packet.getPayload(ESP3_RORG_LENGTH, dataLength)); - bytes[0] = (byte) 0x91; // bidirectional communication, teach in accepted, teach in response + bytes[0] = (byte) (teachIn ? 0x91 : 0xA1); // bidirectional communication, teach in accepted or teach out, teach + // in response setStatus((byte) 0x80); setSuppressRepeating(true); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java index 96d1200b0c4eb..651a786af8d72 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_4BSTeachInVariation3Response.java @@ -23,11 +23,11 @@ */ public class _4BSTeachInVariation3Response extends _4BSMessage { - public _4BSTeachInVariation3Response(ERP1Message packet) { + public _4BSTeachInVariation3Response(ERP1Message packet, boolean teachIn) { byte[] payload = packet.getPayload(ESP3_RORG_LENGTH, RORG._4BS.getDataLength()); - payload[3] = (byte) 0xF0; // telegram with EEP number and Manufacturer ID, - // EEP supported, Sender ID stored, Response + payload[3] = (byte) (teachIn ? 0xF0 : 0xD0); // telegram with EEP number and Manufacturer ID, + // EEP supported, Sender ID stored or deleted, Response setData(payload); setDestinationId(packet.getSenderId()); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java index 4f6afd303d39e..3dd44acc26e6d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Base/_RPSMessage.java @@ -51,4 +51,6 @@ public EEP setStatus(byte status) { return this; } + + public abstract boolean isValidForTeachIn(); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java index c6d54180a628a..018b3ea06335b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/D2_05/D2_05_00.java @@ -98,7 +98,8 @@ protected byte getChannel() { @Override public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) { - discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId()); + discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId()) + .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId()); } @Override diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java index 63e9c0fcf3f43..c84a1a753f7f7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPFactory.java @@ -15,18 +15,24 @@ import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*; import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; import org.openhab.binding.enocean.internal.eep.Base.UTEResponse; import org.openhab.binding.enocean.internal.eep.Base._4BSMessage; import org.openhab.binding.enocean.internal.eep.Base._4BSTeachInVariation3Response; +import org.openhab.binding.enocean.internal.eep.Base._RPSMessage; import org.openhab.binding.enocean.internal.eep.D5_00.D5_00_01; import org.openhab.binding.enocean.internal.eep.F6_01.F6_01_01; import org.openhab.binding.enocean.internal.eep.F6_02.F6_02_01; +import org.openhab.binding.enocean.internal.eep.F6_05.F6_05_02; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_00_EltakoFPE; import org.openhab.binding.enocean.internal.eep.F6_10.F6_10_01; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; +import org.openhab.binding.enocean.internal.messages.Responses.SMACKTeachInResponse; import org.openhab.core.util.HexUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,8 +51,9 @@ public static EEP createEEP(EEPType eepType) { if (cl == null) { throw new IllegalArgumentException("Message " + eepType + " not implemented"); } - return cl.newInstance(); - } catch (IllegalAccessException | InstantiationException e) { + return cl.getDeclaredConstructor().newInstance(); + } catch (IllegalAccessException | InstantiationException | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException(e); } } @@ -69,6 +76,21 @@ public static EEP buildEEP(EEPType eepType, ERP1Message packet) { } } + private static EEPType getGenericEEPTypeFor(byte rorg) { + logger.info("Received unsupported EEP teach in, trying to fallback to generic thing"); + RORG r = RORG.getRORG(rorg); + if (r == RORG._4BS) { + logger.info("Fallback to 4BS generic thing"); + return EEPType.Generic4BS; + } else if (r == RORG.VLD) { + logger.info("Fallback to VLD generic thing"); + return EEPType.GenericVLD; + } else { + logger.info("Fallback not possible"); + return null; + } + } + public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { if (!msg.getIsTeachIn() && !(msg.getRORG() == RORG.RPS)) { return null; @@ -77,38 +99,48 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { switch (msg.getRORG()) { case RPS: try { - EEP result = new F6_01_01(msg); - if (result.isValid()) { // check if t21 is set, nu not set, and data == 0x10 or 0x00 + _RPSMessage result = new F6_10_00(msg); + if (result.isValidForTeachIn()) { + return result; + } + } catch (Exception e) { + } + + try { + _RPSMessage result = new F6_10_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } try { - EEP result = new F6_02_01(msg); - if (result.isValid()) { // check if highest bit is not set + _RPSMessage result = new F6_02_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } try { - EEP result = new F6_10_00(msg); - if (result.isValid()) { + _RPSMessage result = new F6_05_02(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } + try { - EEP result = new F6_10_00_EltakoFPE(msg); - if (result.isValid()) { // check if data == 0x10 or 0x00 + _RPSMessage result = new F6_01_01(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { } + try { - EEP result = new F6_10_01(msg); - if (result.isValid()) { + _RPSMessage result = new F6_10_00_EltakoFPE(msg); + if (result.isValidForTeachIn()) { return result; } } catch (Exception e) { @@ -120,8 +152,8 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { case _4BS: { int db_0 = msg.getPayload()[4]; if ((db_0 & _4BSMessage.LRN_Type_Mask) == 0) { // Variation 1 - logger.info("Received 4BS Teach In variation 1 without EEP"); - return null; + logger.info("Received 4BS Teach In variation 1 without EEP, fallback to generic thing"); + return buildEEP(EEPType.Generic4BS, msg); } byte db_3 = msg.getPayload()[1]; @@ -132,19 +164,21 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { int type = ((db_3 & 0b11) << 5) + ((db_2 & 0xFF) >>> 3); int manufId = ((db_2 & 0b111) << 8) + (db_1 & 0xff); - logger.info("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}", + logger.debug("Received 4BS Teach In with EEP A5-{}-{} and manufacturerID {}", HexUtils.bytesToHex(new byte[] { (byte) func }), HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId })); EEPType eepType = EEPType.getType(RORG._4BS, func, type, manufId); if (eepType == null) { - logger.debug("Received unsupported EEP teach in, fallback to generic thing"); - eepType = EEPType.Generic4BS; + eepType = getGenericEEPTypeFor(RORG._4BS.getValue()); } - return buildEEP(eepType, msg); + if (eepType != null) { + return buildEEP(eepType, msg); + } } + break; case UTE: { byte[] payload = msg.getPayload(); @@ -161,38 +195,58 @@ public static EEP buildEEPFromTeachInERP1(ERP1Message msg) { EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId); if (eepType == null) { - logger.info("Received unsupported EEP teach in, fallback to generic thing"); - RORG r = RORG.getRORG(rorg); - if (r == RORG._4BS) { - eepType = EEPType.Generic4BS; - } else if (r == RORG.VLD) { - eepType = EEPType.GenericVLD; - } else { - return null; - } + eepType = getGenericEEPTypeFor(rorg); } - return buildEEP(eepType, msg); + if (eepType != null) { + return buildEEP(eepType, msg); + } } - case Unknown: - case VLD: - case MSC: - case SIG: + break; + default: return null; } return null; } - public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId) { + public static EEP buildEEPFromTeachInSMACKEvent(EventMessage event) { + if (event.getEventMessageType() != EventMessageType.SA_CONFIRM_LEARN) { + return null; + } + + byte[] payload = event.getPayload(); + byte manufIdMSB = payload[2]; + byte manufIdLSB = payload[3]; + int manufId = ((manufIdMSB & 0b111) << 8) + (manufIdLSB & 0xff); + + byte rorg = payload[4]; + int func = payload[5] & 0xFF; + int type = payload[6] & 0xFF; + + byte[] senderId = Arrays.copyOfRange(payload, 12, 12 + 4); + + logger.debug("Received SMACK Teach In with EEP {}-{}-{} and manufacturerID {}", + HexUtils.bytesToHex(new byte[] { (byte) rorg }), HexUtils.bytesToHex(new byte[] { (byte) func }), + HexUtils.bytesToHex(new byte[] { (byte) type }), HexUtils.bytesToHex(new byte[] { (byte) manufId })); + + EEPType eepType = EEPType.getType(RORG.getRORG(rorg), func, type, manufId); + if (eepType == null) { + eepType = getGenericEEPTypeFor(rorg); + } + + return createEEP(eepType).setSenderId(senderId); + } + + public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] senderId, boolean teachIn) { switch (msg.getRORG()) { case UTE: - EEP result = new UTEResponse(msg); + EEP result = new UTEResponse(msg, teachIn); result.setSenderId(senderId); return result; case _4BS: - result = new _4BSTeachInVariation3Response(msg); + result = new _4BSTeachInVariation3Response(msg, teachIn); result.setSenderId(senderId); return result; @@ -200,4 +254,31 @@ public static EEP buildResponseEEPFromTeachInERP1(ERP1Message msg, byte[] sender return null; } } + + public static SMACKTeachInResponse buildResponseFromSMACKTeachIn(EventMessage event, boolean sendTeachOuts) { + SMACKTeachInResponse response = new SMACKTeachInResponse(); + + byte priority = event.getPayload()[1]; + if ((priority & 0b1001) == 0b1001) { + logger.debug("gtw is already postmaster"); + if (sendTeachOuts) { + logger.debug("Repeated learn is not allow hence send teach out"); + response.setTeachOutResponse(); + } else { + logger.debug("Send a repeated learn in"); + response.setRepeatedTeachInResponse(); + } + } else if ((priority & 0b100) == 0) { + logger.debug("no place for further mailbox"); + response.setNoPlaceForFurtherMailbox(); + } else if ((priority & 0b10) == 0) { + logger.debug("rssi is not good enough"); + response.setBadRSSI(); + } else if ((priority & 0b1) == 0b1) { + logger.debug("gtw is candidate for postmaster => teach in"); + response.setTeachIn(); + } + + return response; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java index 451c853303fdd..26c461ae8257c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/EEPType.java @@ -20,6 +20,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.openhab.binding.enocean.internal.EnOceanChannelDescription; +import org.openhab.binding.enocean.internal.config.EnOceanChannelTransformationConfig; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_01; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_02; import org.openhab.binding.enocean.internal.eep.A5_02.A5_02_03; @@ -164,15 +165,9 @@ public enum EEPType { UTEResponse(RORG.UTE, 0, 0, false, UTEResponse.class, null), _4BSTeachInVariation3Response(RORG._4BS, 0, 0, false, _4BSTeachInVariation3Response.class, null), - GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD), - Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD, CHANNEL_VIBRATION), - GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING, CHANNEL_GENERIC_SWITCH, - CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_DIMMER, CHANNEL_GENERIC_NUMBER, CHANNEL_GENERIC_STRING, - CHANNEL_GENERIC_COLOR, CHANNEL_GENERIC_TEACHINCMD), + GenericRPS(RORG.RPS, 0xFF, 0xFF, false, GenericRPS.class, THING_TYPE_GENERICTHING), + Generic4BS(RORG._4BS, 0xFF, 0xFF, false, Generic4BS.class, THING_TYPE_GENERICTHING, CHANNEL_VIBRATION), + GenericVLD(RORG.VLD, 0xFF, 0xFF, false, GenericVLD.class, THING_TYPE_GENERICTHING), PTM200(RORG.RPS, 0x00, 0x00, false, PTM200Message.class, null, CHANNEL_GENERAL_SWITCHING, CHANNEL_ROLLERSHUTTER, CHANNEL_CONTACT), @@ -391,8 +386,8 @@ public enum EEPType { // UniversalCommand(RORG._4BS, 0x3f, 0x7f, false, A5_3F_7F_Universal.class, THING_TYPE_UNIVERSALACTUATOR, // CHANNEL_GENERIC_ROLLERSHUTTER, CHANNEL_GENERIC_LIGHT_SWITCHING, CHANNEL_GENERIC_DIMMER, CHANNEL_TEACHINCMD), - EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, 0, - new Hashtable() { + EltakoFSB(RORG._4BS, 0x3f, 0x7f, false, false, "EltakoFSB", 0, A5_3F_7F_EltakoFSB.class, THING_TYPE_ROLLERSHUTTER, + 0, new Hashtable() { private static final long serialVersionUID = 1L; { put(CHANNEL_ROLLERSHUTTER, new Configuration()); @@ -404,10 +399,10 @@ public enum EEPType { } }), - Thermostat(RORG._4BS, 0x20, 0x04, false, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION, + Thermostat(RORG._4BS, 0x20, 0x04, false, true, A5_20_04.class, THING_TYPE_THERMOSTAT, CHANNEL_VALVE_POSITION, CHANNEL_BUTTON_LOCK, CHANNEL_DISPLAY_ORIENTATION, CHANNEL_TEMPERATURE_SETPOINT, CHANNEL_TEMPERATURE, CHANNEL_FEED_TEMPERATURE, CHANNEL_MEASUREMENT_CONTROL, CHANNEL_FAILURE_CODE, CHANNEL_WAKEUPCYCLE, - CHANNEL_SERVICECOMMAND, CHANNEL_STATUS_REQUEST_EVENT, CHANNEL_SEND_COMMAND), + CHANNEL_SERVICECOMMAND), SwitchWithEnergyMeasurment_00(RORG.VLD, 0x01, 0x00, true, D2_01_00.class, THING_TYPE_MEASUREMENTSWITCH, CHANNEL_GENERAL_SWITCHING, CHANNEL_TOTALUSAGE), @@ -512,23 +507,36 @@ public enum EEPType { private boolean supportsRefresh; + private boolean requestsResponse; + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { this(rorg, func, type, supportsRefresh, eepClass, thingTypeUID, -1, channelIds); } + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, + Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { + this(rorg, func, type, supportsRefresh, requestsResponse, eepClass, thingTypeUID, -1, channelIds); + } + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, Class eepClass, ThingTypeUID thingTypeUID, String... channelIds) { - this(rorg, func, type, supportsRefresh, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, channelIds); + this(rorg, func, type, supportsRefresh, false, manufactorSuffix, manufId, eepClass, thingTypeUID, 0, + channelIds); } EEPType(RORG rorg, int func, int type, boolean supportsRefresh, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { - this(rorg, func, type, supportsRefresh, "", 0, eepClass, thingTypeUID, command, channelIds); + this(rorg, func, type, supportsRefresh, false, "", 0, eepClass, thingTypeUID, command, channelIds); } - EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { + this(rorg, func, type, supportsRefresh, requestsResponse, "", 0, eepClass, thingTypeUID, command, channelIds); + } + + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix, + int manufId, Class eepClass, ThingTypeUID thingTypeUID, int command, String... channelIds) { this.rorg = rorg; this.func = func; this.type = type; @@ -538,24 +546,18 @@ public enum EEPType { this.manufactorSuffix = manufactorSuffix; this.manufactorId = manufId; this.supportsRefresh = supportsRefresh; + this.requestsResponse = requestsResponse; for (String id : channelIds) { this.channelIdsWithConfig.put(id, new Configuration()); this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); } - this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration()); - this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI)); - - this.channelIdsWithConfig.put(CHANNEL_REPEATCOUNT, new Configuration()); - this.supportedChannels.put(CHANNEL_REPEATCOUNT, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_REPEATCOUNT)); - - this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration()); - this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED)); + addDefaultChannels(); } - EEPType(RORG rorg, int func, int type, boolean supportsRefresh, String manufactorSuffix, int manufId, - Class eepClass, ThingTypeUID thingTypeUID, int command, + EEPType(RORG rorg, int func, int type, boolean supportsRefresh, boolean requestsResponse, String manufactorSuffix, + int manufId, Class eepClass, ThingTypeUID thingTypeUID, int command, Hashtable channelConfigs) { this.rorg = rorg; this.func = func; @@ -567,11 +569,46 @@ public enum EEPType { this.manufactorSuffix = manufactorSuffix; this.manufactorId = manufId; this.supportsRefresh = supportsRefresh; + this.requestsResponse = requestsResponse; for (String id : channelConfigs.keySet()) { this.supportedChannels.put(id, CHANNELID2CHANNELDESCRIPTION.get(id)); } + addDefaultChannels(); + } + + private void addDefaultChannels() { + + if (THING_TYPE_GENERICTHING.equals(this.thingTypeUID)) { + this.channelIdsWithConfig.put(CHANNEL_GENERIC_SWITCH, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_SWITCH, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_SWITCH)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_ROLLERSHUTTER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_ROLLERSHUTTER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_ROLLERSHUTTER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_DIMMER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_DIMMER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_DIMMER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_NUMBER, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_NUMBER, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_NUMBER)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_STRING, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_STRING, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_STRING)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_COLOR, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_COLOR, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_COLOR)); + + this.channelIdsWithConfig.put(CHANNEL_GENERIC_TEACHINCMD, new EnOceanChannelTransformationConfig()); + this.supportedChannels.put(CHANNEL_GENERIC_TEACHINCMD, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_GENERIC_TEACHINCMD)); + } + this.channelIdsWithConfig.put(CHANNEL_RSSI, new Configuration()); this.supportedChannels.put(CHANNEL_RSSI, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_RSSI)); @@ -580,6 +617,12 @@ public enum EEPType { this.channelIdsWithConfig.put(CHANNEL_LASTRECEIVED, new Configuration()); this.supportedChannels.put(CHANNEL_LASTRECEIVED, CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_LASTRECEIVED)); + + if (requestsResponse) { + this.channelIdsWithConfig.put(CHANNEL_STATUS_REQUEST_EVENT, new Configuration()); + this.supportedChannels.put(CHANNEL_STATUS_REQUEST_EVENT, + CHANNELID2CHANNELDESCRIPTION.get(CHANNEL_STATUS_REQUEST_EVENT)); + } } public Class getEEPClass() { @@ -602,6 +645,10 @@ public boolean getSupportsRefresh() { return supportsRefresh; } + public boolean getRequstesResponse() { + return requestsResponse; + } + public Map GetSupportedChannels() { return Collections.unmodifiableMap(supportedChannels); } @@ -614,7 +661,7 @@ public boolean isChannelSupported(Channel channel) { } public boolean isChannelSupported(String channelId, String channelTypeId) { - return supportedChannels.containsKey(channelId) + return supportedChannels.containsKey(channelId) || VIRTUALCHANNEL_SEND_COMMAND.equals(channelId) || supportedChannels.values().stream().anyMatch(c -> c.channelTypeUID.getId().equals(channelTypeId)); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java index ce19528af36f0..b69aa8d4d6386 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_01/F6_01_01.java @@ -34,9 +34,6 @@ public F6_01_01(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } return getBit(bytes[0], 4) ? CommonTriggerEvents.PRESSED : CommonTriggerEvents.RELEASED; } @@ -45,4 +42,10 @@ protected String convertToEventImpl(String channelId, String channelTypeId, Stri protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && !getBit(bytes[0], 7); } + + @Override + public boolean isValidForTeachIn() { + // just treat press as teach in, ignore release + return t21 && !nu && bytes[0] == 0x10; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java index 2af97fdc06f67..8152c5e90a30a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_01.java @@ -55,9 +55,6 @@ public F6_02_01(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } if (t21 && nu) { byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? A0 : B0; @@ -112,11 +109,6 @@ protected State convertToStateImpl(String channelId, String channelTypeId, // this method is used by the classic device listener channels to convert an rocker switch message into an // appropriate item update State currentState = getCurrentStateFunc.apply(channelId); - - if (!isValid()) { - return UnDefType.UNDEF; - } - if (t21 && nu) { EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); byte dir1 = c.getChannel() == Channel.ChannelA ? A0 : B0; @@ -179,4 +171,22 @@ private State inverse(UpDownType currentState) { protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && !getBit(bytes[0], 7); } + + @Override + public boolean isValidForTeachIn() { + if (t21) { + // just treat press as teach in => DB0.4 has to be set + if (!getBit(bytes[0], 4)) { + return false; + } + // DB0.7 is never set for rocker switch message + if (getBit(bytes[0], 7)) { + return false; + } + } else { + return false; + } + + return true; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java index 29b643cb01a1a..76e169785171a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_02/F6_02_02.java @@ -52,9 +52,6 @@ public F6_02_02(ERP1Message packet) { @Override protected String convertToEventImpl(String channelId, String channelTypeId, String lastEvent, Configuration config) { - if (!isValid()) { - return null; - } if (t21 && nu) { byte dir1 = channelId.equals(CHANNEL_ROCKERSWITCH_CHANNELA) ? AI : BI; @@ -109,11 +106,6 @@ protected State convertToStateImpl(String channelId, String channelTypeId, // this method is used by the classic device listener channels to convert an rocker switch message into an // appropriate item update State currentState = getCurrentStateFunc.apply(channelId); - - if (!isValid()) { - return UnDefType.UNDEF; - } - if (t21 && nu) { EnOceanChannelVirtualRockerSwitchConfig c = config.as(EnOceanChannelVirtualRockerSwitchConfig.class); byte dir1 = c.getChannel() == Channel.ChannelA ? AI : BI; @@ -171,4 +163,9 @@ private State inverse(OnOffType currentState) { private State inverse(UpDownType currentState) { return currentState == UpDownType.UP ? UpDownType.DOWN : UpDownType.UP; } + + @Override + public boolean isValidForTeachIn() { + return false; // Never treat a message as F6-02-02, let user decide which orientation of rocker switch is used + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java index 50855ab2e92cc..4fafc5b68ac6e 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_05/F6_05_02.java @@ -44,9 +44,6 @@ public F6_05_02(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } switch (channelId) { case CHANNEL_SMOKEDETECTION: @@ -62,4 +59,10 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && (bytes[0] == ALARM_OFF || bytes[0] == ALARM_ON || bytes[0] == ENERGY_LOW); } + + @Override + public boolean isValidForTeachIn() { + // just treat the first message with ALARM_ON as teach in + return !t21 && !nu && bytes[0] == ALARM_ON; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java index 43fc0f75344d4..498a807458fdb 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00.java @@ -47,9 +47,6 @@ public F6_10_00(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } byte data = (byte) (bytes[0] & 0xF0); @@ -82,4 +79,9 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && getBit(bytes[0], 7) && getBit(bytes[0], 6); } + + @Override + public boolean isValidForTeachIn() { + return t21 && !nu && getBit(bytes[0], 7) && getBit(bytes[0], 6); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java index 8a381ea9e1600..ee9cd485f9c9b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_00_EltakoFPE.java @@ -61,4 +61,10 @@ protected boolean validateData(byte[] bytes) { // FPE just sends 0b00010000 or 0b00000000 value, so we apply mask 0b11101111 return super.validateData(bytes) && ((bytes[0] & (byte) 0xEF) == (byte) 0x00); } + + @Override + public boolean isValidForTeachIn() { + // just treat CLOSED as teach in + return bytes[0] == CLOSED; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java index 51ed9306212f0..3773cafe49c8d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/F6_10/F6_10_01.java @@ -47,9 +47,6 @@ public F6_10_01(ERP1Message packet) { @Override protected State convertToStateImpl(String channelId, String channelTypeId, Function getCurrentStateFunc, Configuration config) { - if (!isValid()) { - return UnDefType.UNDEF; - } byte data = (byte) (bytes[0] & 0x0F); @@ -82,4 +79,10 @@ protected State convertToStateImpl(String channelId, String channelTypeId, protected boolean validateData(byte[] bytes) { return super.validateData(bytes) && getBit(bytes[0], 6) && getBit(bytes[0], 3) && getBit(bytes[0], 2); } + + @Override + public boolean isValidForTeachIn() { + return !getBit(bytes[0], 7) && getBit(bytes[0], 6) && !getBit(bytes[0], 5) && !getBit(bytes[0], 4) + && getBit(bytes[0], 3) && getBit(bytes[0], 2); + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java index 178ae890b8689..d930e8206400b 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/eep/Generic/GenericEEP.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.enocean.internal.eep.Generic; -import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.PARAMETER_EEPID; +import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import static org.openhab.binding.enocean.internal.messages.ESP3Packet.*; import java.lang.reflect.InvocationTargetException; @@ -161,6 +161,7 @@ protected boolean validateData(byte[] bytes) { @Override public void addConfigPropertiesTo(DiscoveryResultBuilder discoveredThingResultBuilder) { - discoveredThingResultBuilder.withProperty(PARAMETER_EEPID, getEEPType().getId()); + discoveredThingResultBuilder.withProperty(PARAMETER_SENDINGEEPID, getEEPType().getId()) + .withProperty(PARAMETER_RECEIVINGEEPID, getEEPType().getId()); } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java index 590769153791d..be2398ac0cf6c 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseActuatorHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.enocean.internal.eep.EEPType; import org.openhab.binding.enocean.internal.messages.BasePacket; import org.openhab.core.config.core.Configuration; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -68,8 +69,8 @@ public EnOceanBaseActuatorHandler(Thing thing, ItemChannelLinkRegistry itemChann * @param senderIdOffset to be validated * @return true if senderIdOffset is between ]0;128[ and is not used yet */ - private boolean validateSenderIdOffset(int senderIdOffset) { - if (senderIdOffset == -1) { + private boolean validateSenderIdOffset(Integer senderIdOffset) { + if (senderIdOffset == null) { return true; } @@ -157,26 +158,24 @@ boolean validateConfig() { } private boolean initializeIdForSending() { - // Generic things are treated as actuator things, however to support also generic sensors one can define a - // senderIdOffset of -1 - // TODO: seperate generic actuators from generic sensors? - String thingTypeId = this.getThing().getThingTypeUID().getId(); - String genericThingTypeId = THING_TYPE_GENERICTHING.getId(); - - if (getConfiguration().senderIdOffset == -1 && thingTypeId.equals(genericThingTypeId)) { - return true; - } - EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); if (bridgeHandler == null) { return false; } - // if senderIdOffset is not set (=> defaults to -1) or set to -1, the next free senderIdOffset is determined - if (getConfiguration().senderIdOffset == -1) { + // Generic things are treated as actuator things, however to support also generic sensors one can omit + // senderIdOffset + // TODO: seperate generic actuators from generic sensors? + if ((getConfiguration().senderIdOffset == null + && THING_TYPE_GENERICTHING.equals(this.getThing().getThingTypeUID()))) { + return true; + } + + // if senderIdOffset is not set, the next free senderIdOffset is determined + if (getConfiguration().senderIdOffset == null) { Configuration updateConfig = editConfiguration(); getConfiguration().senderIdOffset = bridgeHandler.getNextSenderId(thing); - if (getConfiguration().senderIdOffset == -1) { + if (getConfiguration().senderIdOffset == null) { configurationErrorDescription = "Could not get a free sender Id from Bridge"; return false; } @@ -185,12 +184,10 @@ private boolean initializeIdForSending() { } byte[] baseId = bridgeHandler.getBaseId(); - baseId[3] = (byte) ((baseId[3] & 0xFF) + getConfiguration().senderIdOffset); + baseId[3] = (byte) ((baseId[3] + getConfiguration().senderIdOffset) & 0xFF); this.senderId = baseId; - - this.updateProperty(PROPERTY_ENOCEAN_ID, HexUtils.bytesToHex(this.senderId)); + this.updateProperty(PROPERTY_SENDINGENOCEAN_ID, HexUtils.bytesToHex(this.senderId)); bridgeHandler.addSender(getConfiguration().senderIdOffset, thing); - return true; } @@ -203,6 +200,22 @@ private void refreshStates() { } } + @Override + protected void sendRequestResponse() { + sendMessage(VIRTUALCHANNEL_SEND_COMMAND, VIRTUALCHANNEL_SEND_COMMAND, OnOffType.ON, null); + } + + protected void sendMessage(String channelId, String channelTypeId, Command command, Configuration channelConfig) { + EEP eep = EEPFactory.createEEP(sendingEEPType); + if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig) + .hasData()) { + BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId) + .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message(); + + getBridgeHandler().sendMessage(msg, null); + } + } + @Override public void handleCommand(ChannelUID channelUID, Command command) { // We must have a valid sendingEEPType and sender id to send commands @@ -237,16 +250,7 @@ public void handleCommand(ChannelUID channelUID, Command command) { try { Configuration channelConfig = channel.getConfiguration(); - - EEP eep = EEPFactory.createEEP(sendingEEPType); - if (eep.convertFromCommand(channelId, channelTypeId, command, id -> getCurrentState(id), channelConfig) - .hasData()) { - BasePacket msg = eep.setSenderId(senderId).setDestinationId(destinationId) - .setSuppressRepeating(getConfiguration().suppressRepeating).getERP1Message(); - - getBridgeHandler().sendMessage(msg, null); - } - + sendMessage(channelId, channelTypeId, command, channelConfig); } catch (IllegalArgumentException e) { logger.warn("Exception while sending telegram!", e); } @@ -254,11 +258,16 @@ public void handleCommand(ChannelUID channelUID, Command command) { @Override public void handleRemoval() { - if (getConfiguration().senderIdOffset > 0) { - EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); - if (bridgeHandler != null) { + + EnOceanBridgeHandler bridgeHandler = getBridgeHandler(); + if (bridgeHandler != null) { + if (getConfiguration().senderIdOffset != null && getConfiguration().senderIdOffset > 0) { bridgeHandler.removeSender(getConfiguration().senderIdOffset); } + + if (bridgeHandler.isSmackClient(this.thing)) { + logger.warn("Removing smack client (ThingId: {}) without teach out!", this.thing.getUID().getId()); + } } super.handleRemoval(); diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java index d533c1e558c4a..b4fed2ccb4ad7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseSensorHandler.java @@ -19,8 +19,11 @@ import java.util.Comparator; import java.util.Hashtable; import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; +import org.apache.commons.lang3.NotImplementedException; import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig; import org.openhab.binding.enocean.internal.eep.EEP; import org.openhab.binding.enocean.internal.eep.EEPFactory; @@ -57,6 +60,8 @@ public class EnOceanBaseSensorHandler extends EnOceanBaseThingHandler implements protected final Hashtable receivingEEPTypes = new Hashtable<>(); + protected ScheduledFuture responseFuture = null; + public EnOceanBaseSensorHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { super(thing, itemChannelLinkRegistry); } @@ -104,7 +109,7 @@ boolean validateConfig() { } @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { return Long.parseLong(config.enoceanId, 16); } @@ -129,6 +134,10 @@ protected Predicate channelFilter(EEPType eepType, byte[] senderId) { }; } + protected void sendRequestResponse() { + throw new NotImplementedException("Sensor cannot send responses"); + } + @Override public void packetReceived(BasePacket packet) { ERP1Message msg = (ERP1Message) packet; @@ -175,6 +184,15 @@ public void packetReceived(BasePacket packet) { break; } }); + + if (receivingEEPType.getRequstesResponse()) { + // fire trigger for receive + triggerChannel(prepareAnswer, "requestAnswer"); + // Send response after 100ms + if (responseFuture == null || responseFuture.isDone()) { + responseFuture = scheduler.schedule(this::sendRequestResponse, 100, TimeUnit.MILLISECONDS); + } + } } } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java index 20ccfa80b5062..f561db01859cf 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBaseThingHandler.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.enocean.internal.handler; +import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; + import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.Hashtable; @@ -63,18 +65,25 @@ public abstract class EnOceanBaseThingHandler extends ConfigStatusThingHandler { private ItemChannelLinkRegistry itemChannelLinkRegistry; + protected @NonNull ChannelUID prepareAnswer; + public EnOceanBaseThingHandler(Thing thing, ItemChannelLinkRegistry itemChannelLinkRegistry) { super(thing); this.itemChannelLinkRegistry = itemChannelLinkRegistry; + prepareAnswer = new ChannelUID(thing.getUID(), CHANNEL_STATUS_REQUEST_EVENT); } - @SuppressWarnings("null") @Override public void initialize() { logger.debug("Initializing enocean base thing handler."); this.gateway = null; // reset gateway in case we change the bridge this.config = null; - initializeThing((getBridge() == null) ? null : getBridge().getStatus()); + Bridge bridge = getBridge(); + if (bridge == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "A bridge is required"); + } else { + initializeThing(bridge.getStatus()); + } } private void initializeThing(ThingStatus bridgeStatus) { @@ -143,6 +152,10 @@ protected void updateChannels() { String channelId = entry.getKey(); EnOceanChannelDescription cd = entry.getValue().GetSupportedChannels().get(channelId); + if (cd == null) { + return; + } + // if we do not need to auto create channel => skip if (!cd.autoCreate) { return; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java index c888f6a60be2d..e793366f26698 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanBridgeHandler.java @@ -15,30 +15,35 @@ import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import java.io.IOException; -import java.math.BigDecimal; +import java.util.Arrays; import java.util.Collection; import java.util.LinkedList; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import org.openhab.binding.enocean.internal.EnOceanConfigStatusMessage; import org.openhab.binding.enocean.internal.config.EnOceanBaseConfig; import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig; +import org.openhab.binding.enocean.internal.config.EnOceanBridgeConfig.ESPVersion; import org.openhab.binding.enocean.internal.messages.BasePacket; -import org.openhab.binding.enocean.internal.messages.BaseResponse; import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; -import org.openhab.binding.enocean.internal.messages.RDBaseIdResponse; -import org.openhab.binding.enocean.internal.messages.RDRepeaterResponse; -import org.openhab.binding.enocean.internal.messages.RDVersionResponse; import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.binding.enocean.internal.messages.Response.ResponseType; +import org.openhab.binding.enocean.internal.messages.Responses.BaseResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDBaseIdResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDLearnedClientsResponse.LearnedClient; +import org.openhab.binding.enocean.internal.messages.Responses.RDRepeaterResponse; +import org.openhab.binding.enocean.internal.messages.Responses.RDVersionResponse; import org.openhab.binding.enocean.internal.transceiver.EnOceanESP2Transceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanESP3Transceiver; import org.openhab.binding.enocean.internal.transceiver.EnOceanTransceiver; import org.openhab.binding.enocean.internal.transceiver.PacketListener; import org.openhab.binding.enocean.internal.transceiver.ResponseListener; import org.openhab.binding.enocean.internal.transceiver.ResponseListenerIgnoringTimeouts; +import org.openhab.binding.enocean.internal.transceiver.TeachInListener; import org.openhab.binding.enocean.internal.transceiver.TransceiverErrorListener; import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.status.ConfigStatusMessage; @@ -76,9 +81,12 @@ public class EnOceanBridgeHandler extends ConfigStatusBridgeHandler implements T private byte[] baseId = null; private Thing[] sendingThings = new Thing[128]; - private int nextSenderId = 0; private SerialPortManager serialPortManager; + private boolean smackAvailable = false; + private boolean sendTeachOuts = true; + private Set smackClients = Set.of(); + public EnOceanBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) { super(bridge); this.serialPortManager = serialPortManager; @@ -157,13 +165,6 @@ public void initialize() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "SerialPortManager could not be found"); } else { - Object devId = getConfig().get(NEXTSENDERID); - if (devId != null) { - nextSenderId = ((BigDecimal) devId).intValue(); - } else { - nextSenderId = 0; - } - if (connectorTask == null || connectorTask.isDone()) { connectorTask = scheduler.scheduleWithFixedDelay(new Runnable() { @Override @@ -187,9 +188,12 @@ private synchronized void initTransceiver() { switch (c.getESPVersion()) { case ESP2: transceiver = new EnOceanESP2Transceiver(c.path, this, scheduler, serialPortManager); + smackAvailable = false; + sendTeachOuts = false; break; case ESP3: transceiver = new EnOceanESP3Transceiver(c.path, this, scheduler, serialPortManager); + sendTeachOuts = c.sendTeachOuts; break; default: break; @@ -200,6 +204,7 @@ private synchronized void initTransceiver() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING, "starting rx thread..."); transceiver.StartReceiving(scheduler); + logger.info("EnOceanSerialTransceiver RX thread up and running"); if (c.rs485) { if (c.rs485BaseId != null && !c.rs485BaseId.isEmpty()) { @@ -238,6 +243,28 @@ public void responseReceived(RDBaseIdResponse response) { } } }); + + if (c.getESPVersion() == ESPVersion.ESP3) { + logger.debug("set postmaster mailboxes"); + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_POSTMASTER((byte) (c.enableSmack ? 20 : 0)), + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(BaseResponse response) { + + logger.debug("received response for postmaster mailboxes"); + if (response.isOK()) { + updateProperty("Postmaster mailboxes:", + Integer.toString(c.enableSmack ? 20 : 0)); + smackAvailable = c.enableSmack; + refreshProperties(); + } else { + updateProperty("Postmaster mailboxes:", "Not supported"); + smackAvailable = false; + } + } + }); + } } logger.debug("request version info"); @@ -283,7 +310,7 @@ public Collection getConfigStatus() { Collection configStatusMessages = new LinkedList<>(); // The serial port must be provided - String path = (String) getThing().getConfiguration().get(PATH); + String path = getThing().getConfiguration().as(EnOceanBridgeConfig.class).path; if (path == null || path.isEmpty()) { configStatusMessages.add(ConfigStatusMessage.Builder.error(PATH) .withMessageKeySuffix(EnOceanConfigStatusMessage.PORT_MISSING.getMessageKey()).withArguments(PATH) @@ -297,30 +324,33 @@ public byte[] getBaseId() { return baseId.clone(); } - public int getNextSenderId(Thing sender) { - // TODO: change id to enoceanId + public boolean isSmackClient(Thing sender) { + return smackClients.contains(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId); + } + + public Integer getNextSenderId(Thing sender) { return getNextSenderId(sender.getConfiguration().as(EnOceanBaseConfig.class).enoceanId); } - public int getNextSenderId(String senderId) { - if (nextSenderId != 0 && sendingThings[nextSenderId] == null) { - int result = nextSenderId; - Configuration config = getConfig(); - config.put(NEXTSENDERID, null); - updateConfiguration(config); - nextSenderId = 0; + public Integer getNextSenderId(String enoceanId) { + EnOceanBridgeConfig config = getConfigAs(EnOceanBridgeConfig.class); - return result; + if (config.nextSenderId != null && sendingThings[config.nextSenderId] == null) { + Configuration c = this.editConfiguration(); + c.put(PARAMETER_NEXT_SENDERID, null); + updateConfiguration(c); + + return config.nextSenderId; } - for (byte i = 1; i < sendingThings.length; i++) { + for (int i = 1; i < sendingThings.length; i++) { if (sendingThings[i] == null || sendingThings[i].getConfiguration().as(EnOceanBaseConfig.class).enoceanId - .equalsIgnoreCase(senderId)) { + .equalsIgnoreCase(enoceanId)) { return i; } } - return -1; + return null; } public boolean existsSender(int id, Thing sender) { @@ -345,7 +375,7 @@ public void sendMessage(BasePacket message, ResponseListene } public void addPacketListener(PacketListener listener) { - addPacketListener(listener, listener.getSenderIdToListenTo()); + addPacketListener(listener, listener.getEnOceanIdToListenTo()); } public void addPacketListener(PacketListener listener, long senderIdToListenTo) { @@ -355,7 +385,7 @@ public void addPacketListener(PacketListener listener, long senderIdToListenTo) } public void removePacketListener(PacketListener listener) { - removePacketListener(listener, listener.getSenderIdToListenTo()); + removePacketListener(listener, listener.getEnOceanIdToListenTo()); } public void removePacketListener(PacketListener listener, long senderIdToListenTo) { @@ -364,12 +394,74 @@ public void removePacketListener(PacketListener listener, long senderIdToListenT } } - public void startDiscovery(PacketListener teachInListener) { + public void startDiscovery(TeachInListener teachInListener) { transceiver.startDiscovery(teachInListener); + + if (smackAvailable) { + // activate smack teach in + logger.debug("activate smack teach in"); + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(true), + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(BaseResponse response) { + + if (response.isOK()) { + logger.debug("Smack teach in activated"); + } + } + }); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + } + } } public void stopDiscovery() { transceiver.stopDiscovery(); + + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_WR_LEARNMODE(false), null); + refreshProperties(); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + } + } + + private void refreshProperties() { + if (getThing().getStatus() == ThingStatus.ONLINE && smackAvailable) { + + logger.debug("request learned smack clients"); + try { + transceiver.sendBasePacket(ESP3PacketFactory.SA_RD_LEARNEDCLIENTS, + new ResponseListenerIgnoringTimeouts() { + + @Override + public void responseReceived(RDLearnedClientsResponse response) { + + logger.debug("received response for learned smack clients"); + if (response.isValid() && response.isOK()) { + LearnedClient[] clients = response.getLearnedClients(); + updateProperty("Learned smart ack clients", Integer.toString(clients.length)); + updateProperty("Smart ack clients", + Arrays.stream(clients) + .map(x -> String.format("%s (MB Idx: %d)", + HexUtils.bytesToHex(x.clientId), x.mailboxIndex)) + .collect(Collectors.joining(", "))); + smackClients = Arrays.stream(clients).map(x -> HexUtils.bytesToHex(x.clientId)) + .collect(Collectors.toSet()); + } + } + }); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "Smack packet could not be send: " + e.getMessage()); + + } + } } @Override @@ -378,4 +470,8 @@ public void ErrorOccured(Throwable exception) { transceiver = null; updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage()); } + + public boolean sendTeachOuts() { + return sendTeachOuts; + } } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java index 412cd9ad8d8fe..08a1e499e82d4 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/handler/EnOceanClassicDeviceHandler.java @@ -71,7 +71,7 @@ void initializeConfig() { } @Override - public long getSenderIdToListenTo() { + public long getEnOceanIdToListenTo() { return 0; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java index 73551cdc83bbe..f410d6e1aba8a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/ESP3PacketFactory.java @@ -13,8 +13,10 @@ package org.openhab.binding.enocean.internal.messages; import org.openhab.binding.enocean.internal.EnOceanBindingConstants; +import org.openhab.binding.enocean.internal.Helper; import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType; import org.openhab.binding.enocean.internal.messages.CCMessage.CCMessageType; +import org.openhab.binding.enocean.internal.messages.SAMessage.SAMessageType; import org.openhab.core.library.types.StringType; /** @@ -42,6 +44,28 @@ public static BasePacket CO_WR_REPEATER(StringType level) { } } + public static BasePacket SA_WR_LEARNMODE(boolean activate) { + return new SAMessage(SAMessageType.SA_WR_LEARNMODE, + new byte[] { SAMessageType.SA_WR_LEARNMODE.getValue(), (byte) (activate ? 1 : 0), 0, 0, 0, 0, 0 }); + } + + public final static BasePacket SA_RD_LEARNEDCLIENTS = new SAMessage(SAMessageType.SA_RD_LEARNEDCLIENTS); + + public static BasePacket SA_RD_MAILBOX_STATUS(byte[] clientId, byte[] controllerId) { + return new SAMessage(SAMessageType.SA_RD_MAILBOX_STATUS, + Helper.concatAll(new byte[] { SAMessageType.SA_RD_MAILBOX_STATUS.getValue() }, clientId, controllerId)); + } + + public static BasePacket SA_WR_POSTMASTER(byte mailboxes) { + return new SAMessage(SAMessageType.SA_WR_POSTMASTER, + new byte[] { SAMessageType.SA_WR_POSTMASTER.getValue(), mailboxes }); + } + + public static BasePacket SA_WR_CLIENTLEARNRQ(byte manu1, byte manu2, byte rorg, byte func, byte type) { + return new SAMessage(SAMessageType.SA_WR_CLIENTLEARNRQ, + new byte[] { SAMessageType.SA_WR_CLIENTLEARNRQ.getValue(), manu1, manu2, rorg, func, type }); + } + public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byte packetType, byte[] payload) { ESPPacketType type = ESPPacketType.getPacketType(packetType); @@ -50,6 +74,8 @@ public static BasePacket BuildPacket(int dataLength, int optionalDataLength, byt return new Response(dataLength, optionalDataLength, payload); case RADIO_ERP1: return new ERP1Message(dataLength, optionalDataLength, payload); + case EVENT: + return new EventMessage(dataLength, optionalDataLength, payload); default: return null; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java new file mode 100644 index 0000000000000..4cacceba47052 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/EventMessage.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.messages; + +import java.util.stream.Stream; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class EventMessage extends BasePacket { + + public enum EventMessageType { + UNKNOWN((byte) 0x00, 1), + SA_RECLAIM_NOT_SUCCESSFUL((byte) 0x01, 1), + SA_CONFIRM_LEARN((byte) 0x02, 17), + SA_LEARN_ACK((byte) 0x03, 4), + CO_READY((byte) 0x04, 2), + CO_EVENT_SECUREDEVICES((byte) 0x05, 6), + CO_DUTYCYCLE_LIMIT((byte) 0x06, 2), + CO_TRANSMIT_FAILED((byte) 0x07, 2); + + private byte value; + private int dataLength; + + EventMessageType(byte value, int dataLength) { + this.value = value; + this.dataLength = dataLength; + } + + public byte getValue() { + return this.value; + } + + public int getDataLength() { + return dataLength; + } + + public static EventMessageType getEventMessageType(byte value) { + return Stream.of(EventMessageType.values()).filter(t -> t.value == value).findFirst().orElse(UNKNOWN); + } + } + + private EventMessageType type; + + EventMessage(int dataLength, int optionalDataLength, byte[] payload) { + super(dataLength, optionalDataLength, ESPPacketType.EVENT, payload); + + type = EventMessageType.getEventMessageType(payload[0]); + } + + public EventMessageType getEventMessageType() { + return type; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java index 2c8a0699fe366..e7b8f2094d30a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Response.java @@ -57,7 +57,7 @@ public static ResponseType getResponsetype(byte value) { protected ResponseType responseType; protected boolean _isValid = false; - protected Response(int dataLength, int optionalDataLength, byte[] payload) { + public Response(int dataLength, int optionalDataLength, byte[] payload) { super(dataLength, optionalDataLength, ESPPacketType.RESPONSE, payload); try { diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java similarity index 85% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java index 63de3f5cdd984..56a4ccbbf869d 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/BaseResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/BaseResponse.java @@ -10,9 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; /** * diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java similarity index 91% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java index e2220c053f863..b2a8a0da72ea2 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDBaseIdResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDBaseIdResponse.java @@ -10,9 +10,10 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; /** * diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java new file mode 100644 index 0000000000000..481915212bd31 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDLearnedClientsResponse.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.messages.Responses; + +import java.util.Arrays; + +import org.openhab.binding.enocean.internal.Helper; +import org.openhab.binding.enocean.internal.messages.Response; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class RDLearnedClientsResponse extends Response { + + public class LearnedClient { + public byte[] clientId; + public byte[] controllerId; + public int mailboxIndex; + } + + LearnedClient[] learnedClients; + + public RDLearnedClientsResponse(Response response) { + this(response.getPayload().length, response.getOptionalPayload().length, + Helper.concatAll(response.getPayload(), response.getOptionalPayload())); + } + + RDLearnedClientsResponse(int dataLength, int optionalDataLength, byte[] payload) { + super(dataLength, optionalDataLength, payload); + + if (payload == null || ((payload.length - 1) % 9) != 0) { + return; + } else { + _isValid = true; + } + + learnedClients = new LearnedClient[(payload.length - 1) / 9]; + for (int i = 0; i < learnedClients.length; i++) { + LearnedClient client = new LearnedClient(); + client.clientId = Arrays.copyOfRange(payload, 1 + i * 9, 1 + i * 9 + 4); + client.controllerId = Arrays.copyOfRange(payload, 5 + i * 9, 5 + i * 9 + 4); + client.mailboxIndex = payload[9 + i * 9] & 0xFF; + learnedClients[i] = client; + } + } + + public LearnedClient[] getLearnedClients() { + return learnedClients; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java similarity index 93% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java index cecc623b86dcb..440068ba4dcfd 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDRepeaterResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDRepeaterResponse.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import static org.openhab.binding.enocean.internal.EnOceanBindingConstants.*; import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.library.types.StringType; /** diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java similarity index 94% rename from bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java rename to bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java index 1374c56e01970..daedc9aa9ae01 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/RDVersionResponse.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/RDVersionResponse.java @@ -10,11 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.enocean.internal.messages; +package org.openhab.binding.enocean.internal.messages.Responses; import java.util.Arrays; import org.eclipse.jdt.annotation.NonNull; +import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.util.HexUtils; /** diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java new file mode 100644 index 0000000000000..a827922d300fb --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/Responses/SMACKTeachInResponse.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.messages.Responses; + +import org.openhab.binding.enocean.internal.messages.Response; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class SMACKTeachInResponse extends Response { + + // set response time to 250ms + static final byte RESPONSE_TIME_HVALUE = 0; + static final byte RESPONSE_TIME_LVALUE = (byte) 0xFA; + + static final byte TEACH_IN = 0x00; + static final byte TEACH_OUT = 0x20; + static final byte REPEATED_TEACH_IN = 0x01; + static final byte NOPLACE_FOR_MAILBOX = 0x12; + static final byte BAD_RSSI = 0x14; + + public SMACKTeachInResponse() { + super(4, 0, new byte[] { Response.ResponseType.RET_OK.getValue(), RESPONSE_TIME_HVALUE, RESPONSE_TIME_LVALUE, + TEACH_IN }); + } + + public void setTeachOutResponse() { + data[3] = TEACH_OUT; + } + + public boolean isTeachOut() { + return data[3] == TEACH_OUT; + } + + public void setRepeatedTeachInResponse() { + data[3] = REPEATED_TEACH_IN; + } + + public void setNoPlaceForFurtherMailbox() { + data[3] = NOPLACE_FOR_MAILBOX; + } + + public void setBadRSSI() { + data[3] = BAD_RSSI; + } + + public void setTeachIn() { + data[3] = TEACH_IN; + } + + public boolean isTeachIn() { + return data[3] == TEACH_IN; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java new file mode 100644 index 0000000000000..e63087ee58717 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/messages/SAMessage.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.messages; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public class SAMessage extends BasePacket { + + public enum SAMessageType { + SA_WR_LEARNMODE((byte) 0x01, 7), + SA_RD_LEARNMODE((byte) 0x02, 1), + SA_WR_LEARNCONFIRM((byte) 0x03, 1), + SA_WR_CLIENTLEARNRQ((byte) 0x04, 6), + SA_WR_RESET((byte) 0x05, 1), + SA_RD_LEARNEDCLIENTS((byte) 0x06, 1), + SA_WR_RECLAIMS((byte) 0x07, 1), + SA_WR_POSTMASTER((byte) 0x08, 2), + SA_RD_MAILBOX_STATUS((byte) 0x09, 9); + + private byte value; + private int dataLength; + + SAMessageType(byte value, int dataLength) { + this.value = value; + this.dataLength = dataLength; + } + + public byte getValue() { + return this.value; + } + + public int getDataLength() { + return dataLength; + } + } + + private SAMessageType type; + + public SAMessage(SAMessageType type) { + this(type, new byte[] { type.getValue() }); + } + + public SAMessage(SAMessageType type, byte[] payload) { + super(type.getDataLength(), 0, ESPPacketType.SMART_ACK_COMMAND, payload); + + this.type = type; + } + + public SAMessageType getSAMessageType() { + return type; + } +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java index db15a3fec0c5f..28e8b04209630 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanESP3Transceiver.java @@ -18,8 +18,6 @@ import org.openhab.binding.enocean.internal.EnOceanException; import org.openhab.binding.enocean.internal.messages.BasePacket; -import org.openhab.binding.enocean.internal.messages.ERP1Message; -import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; import org.openhab.binding.enocean.internal.messages.ESP3Packet; import org.openhab.binding.enocean.internal.messages.ESP3PacketFactory; import org.openhab.binding.enocean.internal.messages.Response; @@ -138,21 +136,8 @@ protected void processMessage(byte firstByte) { HexUtils.bytesToHex(packet.getPayload())); break; case EVENT: - logger.debug("Event occured: {}", HexUtils.bytesToHex(packet.getPayload())); - break; - case RADIO_ERP1: { - ERP1Message msg = (ERP1Message) packet; - logger.debug("{} with RORG {} for {} payload {} received", - packet.getPacketType().name(), msg.getRORG().name(), - HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex( - Arrays.copyOf(dataBuffer, dataLength + optionalLength))); - - if (msg.getRORG() != RORG.Unknown) { - informListeners(msg); - } else { - logger.debug("Received unknown RORG"); - } - } + case RADIO_ERP1: + informListeners(packet); break; case RADIO_ERP2: break; diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java index df729a1dbc0e2..55be4988b0259 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EnOceanTransceiver.java @@ -27,9 +27,13 @@ import org.openhab.binding.enocean.internal.EnOceanBindingConstants; import org.openhab.binding.enocean.internal.EnOceanException; +import org.openhab.binding.enocean.internal.Helper; import org.openhab.binding.enocean.internal.messages.BasePacket; +import org.openhab.binding.enocean.internal.messages.BasePacket.ESPPacketType; import org.openhab.binding.enocean.internal.messages.ERP1Message; import org.openhab.binding.enocean.internal.messages.ERP1Message.RORG; +import org.openhab.binding.enocean.internal.messages.EventMessage; +import org.openhab.binding.enocean.internal.messages.EventMessage.EventMessageType; import org.openhab.binding.enocean.internal.messages.Response; import org.openhab.core.io.transport.serial.PortInUseException; import org.openhab.core.io.transport.serial.SerialPort; @@ -138,7 +142,8 @@ private synchronized void send() throws IOException { Request currentRequest = null; protected Map> listeners; - protected PacketListener teachInListener; + protected HashSet eventListeners; + protected TeachInListener teachInListener; protected InputStream inputStream; protected OutputStream outputStream; @@ -151,6 +156,7 @@ public EnOceanTransceiver(String path, TransceiverErrorListener errorListener, S requestQueue = new RequestQueue(scheduler); listeners = new HashMap<>(); + eventListeners = new HashSet<>(); teachInListener = null; this.errorListener = errorListener; @@ -192,6 +198,7 @@ public void run() { } }); } + logger.info("EnOceanSerialTransceiver RX thread started"); } public void ShutDown() { @@ -266,36 +273,65 @@ protected int read(byte[] buffer, int length) { } } - protected void informListeners(ERP1Message msg) { + protected void informListeners(BasePacket packet) { try { - byte[] senderId = msg.getSenderId(); + if (packet.getPacketType() == ESPPacketType.RADIO_ERP1) { + ERP1Message msg = (ERP1Message) packet; + byte[] senderId = msg.getSenderId(); + byte[] d = Helper.concatAll(msg.getPayload(), msg.getOptionalPayload()); + + logger.debug("{} with RORG {} for {} payload {} received", packet.getPacketType().name(), + msg.getRORG().name(), HexUtils.bytesToHex(msg.getSenderId()), HexUtils.bytesToHex(d)); + + if (msg.getRORG() != RORG.Unknown) { + if (senderId != null) { + if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] + && senderId[1] == filteredDeviceId[1] && senderId[2] == filteredDeviceId[2]) { + // filter away own messages which are received through a repeater + return; + } - if (senderId != null) { - if (filteredDeviceId != null && senderId[0] == filteredDeviceId[0] && senderId[1] == filteredDeviceId[1] - && senderId[2] == filteredDeviceId[2]) { - // filter away own messages which are received through a repeater - return; - } + if (teachInListener != null && (msg.getIsTeachIn() || msg.getRORG() == RORG.RPS)) { + logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId())); + teachInListener.packetReceived(msg); + return; + } else if (teachInListener == null && msg.getIsTeachIn()) { + logger.info("Discard message because this is a teach-in telegram from {}!", + HexUtils.bytesToHex(msg.getSenderId())); + return; + } - if (teachInListener != null) { - if (msg.getIsTeachIn() || (msg.getRORG() == RORG.RPS)) { - logger.info("Received teach in message from {}", HexUtils.bytesToHex(msg.getSenderId())); - teachInListener.packetReceived(msg); - return; + long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16); + HashSet pl = listeners.get(s); + if (pl != null) { + pl.forEach(l -> l.packetReceived(msg)); + } } } else { - if (msg.getIsTeachIn()) { - logger.info("Discard message because this is a teach-in telegram from {}!", - HexUtils.bytesToHex(msg.getSenderId())); + logger.debug("Received unknown RORG"); + } + } else if (packet.getPacketType() == ESPPacketType.EVENT) { + EventMessage event = (EventMessage) packet; + + byte[] d = Helper.concatAll(packet.getPayload(), packet.getOptionalPayload()); + logger.debug("{} with type {} payload {} received", ESPPacketType.EVENT.name(), + event.getEventMessageType().name(), HexUtils.bytesToHex(d)); + + if (event.getEventMessageType() == EventMessageType.SA_CONFIRM_LEARN) { + byte[] senderId = event.getPayload(EventMessageType.SA_CONFIRM_LEARN.getDataLength() - 5, 4); + + if (teachInListener != null) { + logger.info("Received smart teach in from {}", HexUtils.bytesToHex(senderId)); + teachInListener.eventReceived(event); + return; + } else { + logger.info("Discard message because this is a smart teach-in telegram from {}!", + HexUtils.bytesToHex(senderId)); return; } } - long s = Long.parseLong(HexUtils.bytesToHex(senderId), 16); - HashSet pl = listeners.get(s); - if (pl != null) { - pl.forEach(l -> l.packetReceived(msg)); - } + eventListeners.forEach(l -> l.eventReceived(event)); } } catch (Exception e) { logger.error("Exception in informListeners", e); @@ -354,7 +390,15 @@ public void removePacketListener(PacketListener listener, long senderIdToListenT } } - public void startDiscovery(PacketListener teachInListener) { + public void addEventMessageListener(EventListener listener) { + eventListeners.add(listener); + } + + public void removeEventMessageListener(EventListener listener) { + eventListeners.remove(listener); + } + + public void startDiscovery(TeachInListener teachInListener) { this.teachInListener = teachInListener; } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java new file mode 100644 index 0000000000000..2a27bfb14f15b --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/EventListener.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.transceiver; + +import org.openhab.binding.enocean.internal.messages.EventMessage; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public interface EventListener { + public void eventReceived(EventMessage event); +} diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java index 04e55fdba857b..da161441aa87a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/PacketListener.java @@ -22,5 +22,5 @@ public interface PacketListener { public void packetReceived(BasePacket packet); - public long getSenderIdToListenTo(); + public long getEnOceanIdToListenTo(); } diff --git a/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java new file mode 100644 index 0000000000000..06a26c41800c0 --- /dev/null +++ b/bundles/org.openhab.binding.enocean/src/main/java/org/openhab/binding/enocean/internal/transceiver/TeachInListener.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2010-2021 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.enocean.internal.transceiver; + +/** + * + * @author Daniel Weber - Initial contribution + */ +public interface TeachInListener extends PacketListener, EventListener { + +} diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml index 7115374ab58f8..855048b00add7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/CentralCommand.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml index 6d24ab2084156..93b135f8a779f 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/ClassicDevice.xml @@ -20,7 +20,7 @@
- + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml index 2fa60566e40e8..b42b6b76202a7 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/GenericThing.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml index cd58af2cbf2fa..f7430123b677a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/HeatRecoveryVentilation.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml index 0f095d5ed6e18..08e75f162e9f9 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/MeasurementSwitch.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml index 486ceebf0e2e4..4a51f1614bf98 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Rollershutter.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml index 2be75d8e93033..5f1d44fe7e84a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/Thermostat.xml @@ -18,7 +18,7 @@ EnOceanId of device this thing belongs to true - + Id is used to generate the EnOcean Id (Int between [1-127]). If not specified the next free Id will be determined by bridge diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml index afa99f504c7f8..c712af8cd2d90 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/bridge.xml @@ -31,6 +31,11 @@ true ESP3 + + + Declare Gateway as a SMACK Postmaster and handle SMACK messages + true + true @@ -41,9 +46,15 @@ 00000000 - + + true + + Should a learned in or teach out response been send on a repeated smack teach in request + false + + true - + Defines the next device Id, if empty, the next device id is automatically determined diff --git a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml index 9469d48b8c9af..941eb0d76487a 100644 --- a/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.enocean/src/main/resources/OH-INF/thing/channels.xml @@ -488,13 +488,6 @@ - - Switch - - You can send telegrams to the device by switching this channel on - thermostat - - Switch From 17f70415247d54d76ac4bef4615ec96a8db08d58 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Sat, 20 Feb 2021 17:21:52 +0100 Subject: [PATCH 017/118] [voicerss] Add support for voices (#10184) Signed-off-by: Laurent Garnier --- .../voicerss/internal/VoiceRSSTTSService.java | 2 +- .../voicerss/internal/VoiceRSSVoice.java | 7 +- .../cloudapi/CachedVoiceRSSCloudImpl.java | 15 ++- .../internal/cloudapi/VoiceRSSCloudAPI.java | 5 +- .../internal/cloudapi/VoiceRSSCloudImpl.java | 100 +++++++++++++++--- .../voice/voicerss/tool/CreateTTSCache.java | 21 ++-- 6 files changed, 118 insertions(+), 32 deletions(-) diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java index a9d9110e5d949..6d5475468e961 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSTTSService.java @@ -136,7 +136,7 @@ public AudioStream synthesize(String text, Voice voice, AudioFormat requestedFor // only a default voice try { File cacheAudioFile = voiceRssImpl.getTextToSpeechAsFile(apiKey, trimmedText, - voice.getLocale().toLanguageTag(), getApiAudioFormat(requestedFormat)); + voice.getLocale().toLanguageTag(), voice.getLabel(), getApiAudioFormat(requestedFormat)); if (cacheAudioFile == null) { throw new TTSException("Could not read from VoiceRSS service"); } diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java index ef3f82387066c..02d33f8502550 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/VoiceRSSVoice.java @@ -15,6 +15,7 @@ import java.util.Locale; import org.openhab.core.voice.Voice; +import org.openhab.voice.voicerss.internal.cloudapi.VoiceRSSCloudImpl; /** * Implementation of the Voice interface for VoiceRSS. Label is only "default" @@ -54,7 +55,11 @@ public VoiceRSSVoice(Locale locale, String label) { */ @Override public String getUID() { - return "voicerss:" + locale.toLanguageTag().replaceAll("[^a-zA-Z0-9_]", ""); + String uid = "voicerss:" + locale.toLanguageTag().replaceAll("[^a-zA-Z0-9_]", ""); + if (!label.equals(VoiceRSSCloudImpl.DEFAULT_VOICE)) { + uid += "_" + label.replaceAll("[^a-zA-Z0-9_]", ""); + } + return uid; } /** diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java index 71a16d1ee053e..b94f07cf4793a 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/CachedVoiceRSSCloudImpl.java @@ -55,9 +55,9 @@ public CachedVoiceRSSCloudImpl(String cacheFolderName) { } } - public File getTextToSpeechAsFile(String apiKey, String text, String locale, String audioFormat) + public File getTextToSpeechAsFile(String apiKey, String text, String locale, String voice, String audioFormat) throws IOException { - String fileNameInCache = getUniqueFilenameForText(text, locale); + String fileNameInCache = getUniqueFilenameForText(text, locale, voice); // check if in cache File audioFileInCache = new File(cacheFolder, fileNameInCache + "." + audioFormat.toLowerCase()); if (audioFileInCache.exists()) { @@ -65,7 +65,7 @@ public File getTextToSpeechAsFile(String apiKey, String text, String locale, Str } // if not in cache, get audio data and put to cache - try (InputStream is = super.getTextToSpeech(apiKey, text, locale, audioFormat); + try (InputStream is = super.getTextToSpeech(apiKey, text, locale, voice, audioFormat); FileOutputStream fos = new FileOutputStream(audioFileInCache)) { copyStream(is, fos); // write text to file for transparency too @@ -89,7 +89,7 @@ public File getTextToSpeechAsFile(String apiKey, String text, String locale, Str * * Sample: "en-US_00a2653ac5f77063bc4ea2fee87318d3" */ - private String getUniqueFilenameForText(String text, String locale) { + private String getUniqueFilenameForText(String text, String locale, String voice) { try { byte[] bytesOfMessage = text.getBytes(StandardCharsets.UTF_8); MessageDigest md = MessageDigest.getInstance("MD5"); @@ -101,7 +101,12 @@ private String getUniqueFilenameForText(String text, String locale) { while (hashtext.length() < 32) { hashtext = "0" + hashtext; } - return locale + "_" + hashtext; + String filename = locale + "_"; + if (!DEFAULT_VOICE.equals(voice)) { + filename += voice + "_"; + } + filename += hashtext; + return filename; } catch (NoSuchAlgorithmException ex) { // should not happen logger.error("Could not create MD5 hash for '{}'", text, ex); diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java index 8a7d4b186856b..175f8cfa625f1 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudAPI.java @@ -68,6 +68,8 @@ public interface VoiceRSSCloudAPI { * the text to translate into speech * @param locale * the locale to use + * @param voice + * the voice to use, "default" for the default voice * @param audioFormat * the audio format to use * @return an InputStream to the audio data in specified format @@ -75,5 +77,6 @@ public interface VoiceRSSCloudAPI { * will be raised if the audio data can not be retrieved from * cloud service */ - InputStream getTextToSpeech(String apiKey, String text, String locale, String audioFormat) throws IOException; + InputStream getTextToSpeech(String apiKey, String text, String locale, String voice, String audioFormat) + throws IOException; } diff --git a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java index 4582d8b48e2b3..8d50281b96323 100644 --- a/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java +++ b/bundles/org.openhab.voice.voicerss/src/main/java/org/openhab/voice/voicerss/internal/cloudapi/VoiceRSSCloudImpl.java @@ -21,10 +21,11 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; -import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Stream; @@ -34,7 +35,7 @@ /** * This class implements the Cloud service from VoiceRSS. For more information, - * see API documentation at http://www.voicerss.org/api/documentation.aspx. + * see API documentation at http://www.voicerss.org/api . * * Current state of implementation: *