[openwebnet] add sendMessage rule actions to send generic OWN messages on the BUS (#16691)

* [openwebnet] Added OpenWebNetBridgeActions class and sendMessage action

Signed-off-by: Massimo Valla <mvcode00@gmail.com>
This commit is contained in:
M Valla 2024-04-28 11:55:28 +02:00 committed by GitHub
parent dbe970981c
commit 1fff7eb028
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 262 additions and 59 deletions

View File

@ -215,13 +215,13 @@ OPEN command to execute: *5*8#134##
### Lighting, Automation, Basic/CEN/CEN+ Scenario Events, Dry Contact / IR Interfaces, Power and AUX channels ### Lighting, Automation, Basic/CEN/CEN+ Scenario Events, Dry Contact / IR Interfaces, Power and AUX channels
| Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write | | Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write |
|-----------------------------------------|---------------------------------------------------------------|---------------|-----------------------------------------------------------------------------------------------------------------------|:-----------:| |-----------------------------------------|---------------------------------------------------------------|---------------|---------------------------------------------------------------------------------------------------------------|:-----------:|
| `switch` or `switch_01`/`02` for Zigbee | `bus_on_off_switch`, `zb_on_off_switch`, `zb_on_off_switch2u` | Switch | To switch the device `ON` and `OFF` | R/W | | `switch` or `switch_01`/`02` for Zigbee | `bus_on_off_switch`, `zb_on_off_switch`, `zb_on_off_switch2u` | Switch | To switch the device `ON` and `OFF` | R/W |
| `brightness` | `bus_dimmer`, `zb_dimmer` | Dimmer | To adjust the brightness value (Percent, `ON`, `OFF`) | R/W | | `brightness` | `bus_dimmer`, `zb_dimmer` | Dimmer | To adjust the brightness value (Percent, `ON`, `OFF`) | R/W |
| `shutter` | `bus_automation` | Rollershutter | To activate roller shutters (`UP`, `DOWN`, `STOP`, Percent - [see Shutter position](#shutter-position)) | R/W | | `shutter` | `bus_automation` | Rollershutter | To activate roller shutters (`UP`, `DOWN`, `STOP`, Percent - [see Shutter position](#shutter-position)) | R/W |
| `scenario`   | `bus_scenario_control` | String | Trigger channel for Basic scenario events [see possible values](#scenario-channels) | R (TRIGGER) | | `scenario`   | `bus_scenario_control` | String | Trigger channel for Basic scenario events [see possible values](#scenario-channels) | R (TRIGGER) |
| `button#X` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | String | Trigger channel for CEN/CEN+ scenario events [see possible values](#scenario-channels) | R (TRIGGER) | | `button#X` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | String | Trigger channel for CEN/CEN+ scenario events [see possible values](#scenario-channels) | R (TRIGGER) |
| `sensor` | `bus_dry_contact_ir` | Switch | Indicates if a Dry Contact Interface is `ON`/`OFF`, or if an IR Sensor is detecting movement (`ON`), or not (`OFF`) | R | | `sensor` | `bus_dry_contact_ir` | Switch | If a Dry Contact Interface is `ON`/`OFF`, or if an IR Sensor is detecting movement (`ON`), or not (`OFF`) | R |
| `power` | `bus_energy_meter` | Number:Power | The current active power usage from Energy Meter | R | | `power` | `bus_energy_meter` | Number:Power | The current active power usage from Energy Meter | R |
| `energyToday` | `bus_energy_meter` | Number:Energy | Current day energy | R | | `energyToday` | `bus_energy_meter` | Number:Energy | Current day energy | R |
| `energyThisMonth` | `bus_energy_meter` | Number:Energy | Current month energy | R | | `energyThisMonth` | `bus_energy_meter` | Number:Energy | Current month energy | R |
@ -230,7 +230,7 @@ OPEN command to execute: *5*8#134##
### Alarm channels ### Alarm channels
| Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write | | Channel Type ID (channel ID) | Applies to Thing Type IDs | Item Type | Description | Read/Write |
|------------------------------|----------------------------------------|-------------|-----------------------------------------------------------------------|:-----------:| |------------------------------|----------------------------------------|-------------|--------------------------------------------------------------------------------|:-----------:|
| `state` | `bus_alarm_system`, `bus_alarm_zone` | Switch | Alarm system or zone is active (`ON`) or inactive (`OFF`) | R | | `state` | `bus_alarm_system`, `bus_alarm_zone` | Switch | Alarm system or zone is active (`ON`) or inactive (`OFF`) | R |
| `network` | `bus_alarm_system` | Switch | Alarm system network state (`ON` = network ok, `OFF` = no network) | R | | `network` | `bus_alarm_system` | Switch | Alarm system network state (`ON` = network ok, `OFF` = no network) | R |
| `battery` | `bus_alarm_system` | String | Alarm system battery state (`OK`, `FAULT`, `UNLOADED`) | R | | `battery` | `bus_alarm_system` | String | Alarm system battery state (`OK`, `FAULT`, `UNLOADED`) | R |
@ -323,6 +323,50 @@ In order to activate one specific weekly or scenario program two different chann
Example: to activate SCENARIO number 9 on the thermo Central Unit then set channel `mode` = `SCENARIO` and channel `scenarioProgram` = `9`. Example: to activate SCENARIO number 9 on the thermo Central Unit then set channel `mode` = `SCENARIO` and channel `scenarioProgram` = `9`.
## Rule Actions
The following Rule actions can be used to send arbitrary OpenWebNet messages on the MyHOME BUS.
Actions can be used for example to send commands to the BUS for a WHOs not yet supported by the binding.
- `Boolean sendMessage(String message)` returns a `Boolean` = `true` if the `message` (OpenWebNet frame) was successfully sent via the gateway, `false` otherwise.
- `Map<String, Object> sendMessageWithResponse(String message)` same as previous one, but returns a `Map<String, Object>` with following keys:
- `success`: a `Boolean` = `true` if the `message` was sent successfully
- `responseMessages`: a `List<String>` object containing all returned frames as response to command sent
Usage example:
:::: tabs
::: tab DSL
```java
val actions = getActions("openwebnet", "openwebnet:bus_gateway:mybridge")
var success = actions.sendMessage("*22*22#4#9*2#1##")
logInfo("EventLog", "Success: " + success)
var result = actions.sendMessageWithResponse("*22*22#4#9*2#1##")
logInfo("EventLog", "Success: " + result.get("success"))
logInfo("EventLog", "Response: " + result.get("responseMessages"))
```
:::
::: tab JavaScript
```javascript
var actions = getActions("openwebnet", "openwebnet:bus_gateway:mybridge");
var success = actions.sendMessage("*22*22#4#9*2#1##");
logInfo("EventLog", "Success: " + success);
var result = actions.sendMessageWithResponse("*22*22#4#9*2#1##");
logInfo("EventLog", "Success: " + result.success);
logInfo("EventLog", "Response: " + result.responseMessages);
```
:::
::::
## Full Example ## Full Example
### openwebnet.things: ### openwebnet.things:

View File

@ -19,7 +19,7 @@
<dependency> <dependency>
<groupId>io.github.openwebnet4j</groupId> <groupId>io.github.openwebnet4j</groupId>
<artifactId>openwebnet4j</artifactId> <artifactId>openwebnet4j</artifactId>
<version>0.13.0</version> <version>0.14.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>

View File

@ -0,0 +1,140 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.openwebnet.internal.actions;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.internal.handler.OpenWebNetBridgeHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.openwebnet4j.OpenGateway;
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.communication.Response;
import org.openwebnet4j.message.BaseOpenMessage;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.OpenMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link OpenWebNetBridgeActions} defines the Bridge actions for the
* openwebnet binding.
*
* @author Massimo Valla - Initial contribution
*/
@ThingActionsScope(name = "openwebnet")
@NonNullByDefault
public class OpenWebNetBridgeActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(OpenWebNetBridgeActions.class);
private @Nullable OpenWebNetBridgeHandler bridgeHandler;
@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.bridgeHandler = (OpenWebNetBridgeHandler) handler;
}
@Override
public @Nullable ThingHandler getThingHandler() {
return bridgeHandler;
}
@RuleAction(label = "sendMessage", description = "@text/action.sendMessage.desc")
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendMessage(
@ActionInput(name = "message", label = "message", description = "@text/action.sendMessage.input.message.desc") @Nullable String message) {
@Nullable
Boolean s = (Boolean) sendMessageInternal(message).get("success");
if (s != null) {
return s;
} else {
return Boolean.FALSE;
}
}
@RuleAction(label = "sendMessageWithResponse", description = "@text/action.sendMessageWithResponse.desc")
public @ActionOutput(name = "success", type = "java.lang.Boolean") @ActionOutput(name = "responseMessages", type = "java.util.List<String>") Map<String, Object> sendMessageWithResponse(
@ActionInput(name = "message", label = "message", description = "@text/action.sendMessage.input.message.desc") @Nullable String message) {
return sendMessageInternal(message);
}
private Map<String, Object> sendMessageInternal(@Nullable String message) {
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("success", Boolean.FALSE);
responseMap.put("responseMessages", Collections.emptyList());
if (message == null || message.isBlank()) {
logger.warn("openwebnet sendMessage: cannot send message, message is null or empty");
return responseMap;
}
OpenWebNetBridgeHandler handler = bridgeHandler;
if (handler == null) {
logger.warn("openwebnet sendMessage: cannot send message, bridgeHandler is null.");
return responseMap;
}
OpenMessage msg;
try {
msg = BaseOpenMessage.parse(message);
} catch (FrameException e) {
logger.warn("openwebnet skipping sending message '{}': {}.", message, e.getMessage());
return responseMap;
}
OpenGateway gw = handler.getGateway();
if (gw != null && gw.isConnected()) {
try {
Response res = gw.send(msg);
logger.debug("sent message {} to gateway. Response: {}.", msg, res.getResponseMessages());
responseMap.put("success", res.isSuccess());
List<String> resultList = res.getResponseMessages().stream().map(rm -> rm.getFrameValue())
.collect(Collectors.toList());
responseMap.put("responseMessages", resultList);
return responseMap;
} catch (OWNException e) {
logger.warn("openwebnet exception while sending message '{}' to gateway: {}.", msg, e.getMessage());
return responseMap;
}
} else {
logger.warn("openwebnet skipping sendMessage for bridge {}: gateway is not connected.",
handler.getThing().getUID());
return responseMap;
}
}
// legacy delegate methods
public static Boolean sendMessage(@Nullable ThingActions actions, String message) {
if (actions instanceof OpenWebNetBridgeActions openwebnetBridgeActions) {
return openwebnetBridgeActions.sendMessage(message);
} else {
throw new IllegalArgumentException("Instance is not an OpenWebNetBridgeActions class.");
}
}
public static Map<String, Object> sendMessageWithResponse(@Nullable ThingActions actions, String message) {
if (actions instanceof OpenWebNetBridgeActions openwebnetBridgeActions) {
return openwebnetBridgeActions.sendMessageWithResponse(message);
} else {
throw new IllegalArgumentException("Instance is not an OpenWebNetBridgeActions class.");
}
}
}

View File

@ -30,7 +30,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* The {@link OpenWebNetCENActions} defines CEN/CEN+ actions for the openwebnet binding. * The {@link OpenWebNetCENActions} defines CEN/CEN+ actions for the openwebnet
* binding.
* *
* @author Massimo Valla - Initial contribution * @author Massimo Valla - Initial contribution
*/ */
@ -53,10 +54,10 @@ public class OpenWebNetCENActions implements ThingActions {
return scenarioHandler; return scenarioHandler;
} }
@RuleAction(label = "virtualPress", description = "Virtual press of the push button") @RuleAction(label = "virtualPress", description = "@text/action.virtualPress.desc")
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean virtualPress( public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean virtualPress(
@ActionInput(name = "press", label = "press", description = "Type of press") @Nullable String press, @ActionInput(name = "press", label = "press", description = "@text/action.virtualPress.input.press.desc") @Nullable String press,
@ActionInput(name = "button", label = "button", description = "Button number") int button) { @ActionInput(name = "button", label = "button", description = "@text/action.virtualPress.input.button.desc") int button) {
OpenWebNetScenarioHandler handler = scenarioHandler; OpenWebNetScenarioHandler handler = scenarioHandler;
if (handler == null) { if (handler == null) {
logger.warn("openwebnet OpenWebNetCENActions: scenarioHandler is null!"); logger.warn("openwebnet OpenWebNetCENActions: scenarioHandler is null!");
@ -86,9 +87,9 @@ public class OpenWebNetCENActions implements ThingActions {
} }
// legacy delegate methods // legacy delegate methods
public static void virtualPress(@Nullable ThingActions actions, @Nullable String press, int button) { public static Boolean virtualPress(@Nullable ThingActions actions, @Nullable String press, int button) {
if (actions instanceof OpenWebNetCENActions openWebNetCENActions) { if (actions instanceof OpenWebNetCENActions openWebNetCENActions) {
openWebNetCENActions.virtualPress(press, button); return openWebNetCENActions.virtualPress(press, button);
} else { } else {
throw new IllegalArgumentException("Instance is not an OpenWebNetCENActions class."); throw new IllegalArgumentException("Instance is not an OpenWebNetCENActions class.");
} }

View File

@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants; import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
import org.openhab.binding.openwebnet.internal.actions.OpenWebNetBridgeActions;
import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService; import org.openhab.binding.openwebnet.internal.discovery.OpenWebNetDeviceDiscoveryService;
import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig; import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetBusBridgeConfig;
import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig; import org.openhab.binding.openwebnet.internal.handler.config.OpenWebNetZigBeeBridgeConfig;
@ -271,9 +272,18 @@ public class OpenWebNetBridgeHandler extends ConfigStatusBridgeHandler implement
reconnecting = false; reconnecting = false;
} }
/**
* Return the OpenGateway linked to this BridgeHandler
*
* @return the linked OpenGateway
*/
public @Nullable OpenGateway getGateway() {
return gateway;
}
@Override @Override
public Collection<Class<? extends ThingHandlerService>> getServices() { public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(OpenWebNetDeviceDiscoveryService.class); return Set.of(OpenWebNetDeviceDiscoveryService.class, OpenWebNetBridgeActions.class);
} }
/** /**

View File

@ -309,7 +309,6 @@ public class OpenWebNetThermoregulationHandler extends OpenWebNetThingHandler {
} }
} catch (MalformedFrameException | OWNException e) { } catch (MalformedFrameException | OWNException e) {
logger.warn("handleSetpoint() {}", e.getMessage()); logger.warn("handleSetpoint() {}", e.getMessage());
} }
} else { } else {
logger.info("handleSetpoint() Setpoint temperature must be between 5°C and 40°C for thing {}", logger.info("handleSetpoint() Setpoint temperature must be between 5°C and 40°C for thing {}",

View File

@ -37,7 +37,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class SerialPortAdapter implements org.openwebnet4j.communication.serial.spi.SerialPort { public class SerialPortAdapter implements org.openwebnet4j.communication.serial.spi.SerialPort {
private static final Logger logger = LoggerFactory.getLogger(SerialPortAdapter.class); private final Logger logger = LoggerFactory.getLogger(SerialPortAdapter.class);
private static final int OPEN_TIMEOUT_MS = 200; private static final int OPEN_TIMEOUT_MS = 200;

View File

@ -321,3 +321,12 @@ offline.conf-error-no-bridge = No bridge associated. Assign a bridge in configur
offline.conf-error-auth = Authentication failed. Check gateway password in configuration offline.conf-error-auth = Authentication failed. Check gateway password in configuration
offline.bridge-offline = Bridge offline offline.bridge-offline = Bridge offline
unknown.waiting-state = Waiting state update... unknown.waiting-state = Waiting state update...
# actions
action.sendMessage.desc = Sends a message to the gateway
action.sendMessage.input.message.desc = The message to send
action.sendMessageWithResponse.desc = Sends a message to the gateway and returns the response messages
action.virtualPress.desc = Virtual press of a push button
action.virtualPress.input.press.desc = Type of press
action.virtualPress.input.button.desc = Button number