| Lighting | `1` | `bus_on_off_switch`, `bus_dimmer` | BUS switches and dimmers | Successfully tested: F411/2, F411/4, F411U2, F422, F429. Some discovery issues reported with F429 (DALI Dimmers) |
| Automation | `2` | `bus_automation` | BUS roller shutters, with position feedback and auto-calibration | Successfully tested: LN4672M2 |
| Temperature Control | `4` | `bus_thermo_zone`, `bus_thermo_sensor` | Thermo zones management and temperature sensors (probes). NOTE Central Units (4 or 99 zones) are not fully supported yet. See [Channels - Thermo](#configuring-thermo) for more details. | Successfully tested: H/LN4691, HS4692, KG4691; thermo sensors: L/N/NT4577 + 3455 |
+| CEN & CEN+ Scenarios | `15` & `25` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | CEN/CEN+ scenarios events and virtual activation | Successfully tested: scenario buttons: HC/HD/HS/L/N/NT4680 |
| Energy Management | `18` | `bus_energy_meter` | Energy Management | Successfully tested: F520, F521 |
### For ZigBee (Radio)
- Once the gateway is online, a second Inbox Scan will discover BUS devices
- BUS/SCS Dimmers must be ON and dimmed (30%-100%) during a Scan, otherwise they will be discovered as simple On/Off switches
- *KNOWN ISSUE*: In some cases dimmers connected to a F429 Dali-interface are not automatically discovered
+- CEN/CEN+ Scenario Control devices will be discovered by activation only. See [discovery by activation](#discovery-by-activation) for details. After confirming a discovered CEN/CEN+ device from Inbox, activate again its scenario buttons to add button channels automatically
#### Discovery by Activation
### Configuring Devices
Devices can be discovered automatically using an Inbox Scan after a gateway has been configured and connected.
+
For any manually added device, you must configure:
- the associated gateway (`Parent Bridge` menu)
- the `where` config parameter (`OpenWebNet Device Address`):
- - example for BUS/SCS device with WHERE address Point to Point `A=2 PL=4` --> `where="24"`
- - example for BUS/SCS device with WHERE address Point to Point `A=03 PL=11` on local bus --> `where="0311#4#01"`
- - example for ZigBee devices: `where=765432101#9`. The ID of the device (ADDR part) is usually written in hexadecimal on the device itself, for example `ID 0074CBB1`: convert to decimal (`7654321`) and add `01#9` at the end to obtain `where=765432101#9`. For 2-unit switch devices (`zb_on_off_switch2u`), last part should be `00#9`.
+ - example for BUS/SCS:
+ - light device with WHERE address Point to Point `A=2 PL=4` --> `where="24"`
+ - light device with WHERE address Point to Point `A=03 PL=11` on local bus --> `where="0311#4#01"`
+ - CEN scenario with WHERE address Point to Point `A=05 PL=12` --> `where="0512"`
+ - CEN+ configured scenario `5`: add a `2` before --> `where="25"`
+ - example for ZigBee devices: `where=765432101#9`. The ID of the device (ADDR part) is usually written in hexadecimal on the device itself, for example `ID 0074CBB1`: convert to decimal (`7654321`) and add `01#9` at the end to obtain `where=765432101#9`. For 2-unit switch devices (`zb_on_off_switch2u`), last part should be `00#9`.
+
#### Configuring Thermo
Thermo zones can be configured defining a `bus_thermo_zone` Thing for each zone with the following parameters:
- the `where` config parameter (`OpenWebNet Device Address`):
- - example BUS/SCS Thermo zone `1` --> `where="1"`
+ - example BUS/SCS Thermo zone `1` --> `where="1"`
- the `standAlone` config parameter (`boolean`, default: `true`): identifies if the zone is managed or not by a Central Unit (4 or 99 zones). `standAlone=true` means no Central Unit is present in the system.
Temperature sensors can be configured defining a `bus_thermo_sensor` Thing with the following parameters:
- the `where` config parameter (`OpenWebNet Device Address`):
- - example sensor `5` of external zone `00` --> `where="500"`
- - example: slave sensor `3` of zone `2` --> `where="302"`
+ - example sensor `5` of external zone `00` --> `where="500"`
+ - example: slave sensor `3` of zone `2` --> `where="302"`
#### NOTE
## Channels
-### Lighting, Automation and Power meter channels
+### Lighting, Automation, Power meter and CEN/CEN+ Scenario Events channels
| 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 |
| `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 |
+| `button#X` | `bus_cen_scenario_control`, `bus_cenplus_scenario_control` | String | Trigger channel for CEN/CEN+ scenario events [see possible values](#cen-cen-channels) | R (TRIGGER) |
| `power` | `bus_energy_meter` | Number:Power | The current active power usage from Energy Meter | R |
### Thermo channels
- if OH is restarted the binding does not know if a shutter position has changed in the meantime, so its position will be `UNDEF`. Move the shutter all `UP`/`DOWN` to synchronize again its position with the binding
- the shutter position is estimated based on UP/DOWN timing: an error of ±2% is normal
+#### CEN/CEN+ channels
+
+CEN/CEN+ are [TRIGGER channels](https://www.openhab.org/docs/configuration/rules-dsl.html#channel-based-triggers]): they handle events and do not have a state.
+
+A powerful feature is to be able to assign CEN or CEN+ commands to your physical wall switches and use the events they generate to trigger rules in openHAB: this way openHAB becomes a very powerful scenario manager activated by physical BTicino switches.
+See [openwebnet.rules](#openwebnet-rules) for an example on how to define rules that trigger on CEN/CEN+ buttons events.
+
+It's also possible to send *virtual press* events on the BUS, for example to enable the activation of MH202 scenarios from openHAB.
+See [openwebnet.sitemap](#openwebnet-sitemap) & [openwebnet.rules](#openwebnet-rules) sections for an example on how to use the `virtualPress` action connected to a pushbutton on a sitemap.
+
+- channels are named `button#X` where `X` is the button number on the Scenario Control device
+- in the .thing file configuration you can specify the `buttons` parameter to define a comma-separated list of buttons numbers [0-31] configured for the scenario device, example: `buttons=1,2,4`
+- possible events are:
+ - for CEN:
+ - `START_PRESS` - sent when you start pressing the button
+ - `SHORT_PRESS` - sent if you pressed the button shorter than 0,5sec (sent at the moment when you release it)
+ - `EXTENDED_PRESS` - sent if you keep the button pressed longer than 0,5sec; will be sent again every 0,5sec as long as you hold pressed (good for dimming rules)
+ - `RELEASE_EXTENDED_PRESS` - sent once when you finally release the button after having it pressed longer than 0,5sec
+ - for CEN+:
+ - `SHORT_PRESS` - sent if you pressed the button shorter than 0,5sec (sent at the moment when you release it)
+ - `START_EXTENDED_PRESS` - sent once as soon as you keep the button pressed longer than 0,5sec
+ - `EXTENDED_PRESS` - sent after `START_EXTENDED_PRESS` if you keep the button pressed longer; will be sent again every 0,5sec as long as you hold pressed (good for dimming rules)
+ - `RELEASE_EXTENDED_PRESS` - sent once when you finally release the button after having it pressed longer than 0,5sec
+
+
## Full Example
### openwebnet.things:
```
Bridge openwebnet:bus_gateway:mybridge "MyHOMEServer1" [ host="192.168.1.35", passwd="abcde", port=20000, discoveryByActivation=false ] {
- bus_on_off_switch LR_switch "Living Room Light" [ where="51" ]
- bus_dimmer LR_dimmer "Living Room Dimmer" [ where="0311#4#01" ]
- bus_automation LR_shutter "Living Room Shutter" [ where="93", shutterRun="10050"]
- bus_energy_meter CENTRAL_Ta "Energy Meter Ta" [ where="51" ]
- bus_energy_meter CENTRAL_Tb "Energy Meter Tb" [ where="52" ]
- bus_thermo_zone LR_zone "Living Room Zone" [ where="2"]
- bus_thermo_sensor EXT_tempsensor "External Temperature" [ where="500"]
+ bus_on_off_switch LR_switch "Living Room Light" [ where="51" ]
+ bus_dimmer LR_dimmer "Living Room Dimmer" [ where="0311#4#01" ]
+ bus_automation LR_shutter "Living Room Shutter" [ where="93", shutterRun="10050"]
+ bus_energy_meter CENTRAL_Ta "Energy Meter Ta" [ where="51" ]
+ bus_energy_meter CENTRAL_Tb "Energy Meter Tb" [ where="52" ]
+ bus_thermo_zone LR_zone "Living Room Zone" [ where="2"]
+ bus_thermo_sensor EXT_tempsensor "External Temperature" [ where="500"]
+ bus_cen_scenario_control LR_CEN_scenario "Living Room CEN" [ where="51", buttons="4,3,8"]
+ bus_cenplus_scenario_control LR_CENplus_scenario "Living Room CEN+" [ where="212", buttons="1,5,18" ]
}
```
Number:Temperature iEXT_temp "Temperature [%.1f %unit%]" (gExternal) { channel="openwebnet:bus_thermo_sensor:mybridge:EXT_tempsensor:temperature" }
+String iCENPlusProxyItem "CEN+ Proxy Item"
+
```
Default item=iLR_zone_hv label="Heating valves status"
Default item=iLR_zone_cv label="Conditioning valves status"
}
+
+ Frame label="CEN+ Scenario activation"
+ {
+ Switch item=iCENPlusProxyItem label="My CEN+ scenario" icon="movecontrol" mappings=[ON="Activate"]
+ }
}
```
+### openwebnet.rules
+
+```xtend
+rule "CEN+ virtual press from OH button"
+/* This rule triggers when the proxy item iCENPlusProxyItem is activated, for example from a button on WebUI/sitemap.
+When activated it sends a "virtual short press" event (where=212, button=5) on the BUS
+*/
+when
+ Item iCENPlusProxyItem received command
+then
+ val actions = getActions("openwebnet","openwebnet:bus_cenplus_scenario_control:mybridge:212")
+ actions.virtualPress("SHORT_PRESS", 5)
+end
+
+
+rule "CEN dimmer increase"
+// A "start press" event on CEN where=51, button=4 will increase dimmer%
+when
+ Channel "openwebnet:bus_cen_scenario_control:mybridge:51:button#4" triggered START_PRESS
+then
+ sendCommand(iLR_dimmer, INCREASE)
+end
+
+
+rule "CEN dimmer decrease"
+// A "release extended press" event on CEN where=51, button=4 will decrease dimmer%
+when
+ Channel "openwebnet:bus_cen_scenario_control:mybridge:51:button#4" triggered RELEASE_EXTENDED_PRESS
+then
+ sendCommand(iLR_dimmer, DECREASE)
+end
+```
+
## Notes
- The OpenWebNet protocol is maintained and Copyright by BTicino/Legrand. The documentation of the protocol if freely accessible for developers on the [Legrand developer web site](https://developer.legrand.com/documentation/open-web-net-for-myhome/)
<dependency>
<groupId>io.github.openwebnet4j</groupId>
<artifactId>openwebnet4j</artifactId>
- <version>0.5.3</version>
+ <version>0.6.0</version>
<scope>compile</scope>
</dependency>
public static final String THING_LABEL_BUS_THERMO_SENSOR = "Thermo Sensor";
public static final ThingTypeUID THING_TYPE_BUS_THERMO_ZONE = new ThingTypeUID(BINDING_ID, "bus_thermo_zone");
public static final String THING_LABEL_BUS_THERMO_ZONE = "Thermo Zone";
+ public static final ThingTypeUID THING_TYPE_BUS_CEN_SCENARIO_CONTROL = new ThingTypeUID(BINDING_ID,
+ "bus_cen_scenario_control");
+ public static final String THING_LABEL_BUS_CEN_SCENARIO_CONTROL = "CEN Control";
+ public static final ThingTypeUID THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL = new ThingTypeUID(BINDING_ID,
+ "bus_cenplus_scenario_control");
+ public static final String THING_LABEL_BUS_CENPLUS_SCENARIO_CONTROL = "CEN+ Control";
// ZIGBEE
public static final ThingTypeUID THING_TYPE_ZB_ON_OFF_SWITCH = new ThingTypeUID(BINDING_ID, "zb_on_off_switch");
// ## Automation
public static final Set<ThingTypeUID> AUTOMATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_AUTOMATION,
THING_TYPE_BUS_AUTOMATION);
-
// ## Thermoregulation
public static final Set<ThingTypeUID> THERMOREGULATION_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_THERMO_ZONE,
THING_TYPE_BUS_THERMO_SENSOR);
-
// ## Energy Management
public static final Set<ThingTypeUID> ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_ENERGY_METER);
-
+ // ## CEN/CEN+ Scenario
+ public static final Set<ThingTypeUID> SCENARIO_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BUS_CEN_SCENARIO_CONTROL,
+ THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL);
// ## Groups
public static final Set<ThingTypeUID> DEVICE_SUPPORTED_THING_TYPES = Stream
.of(LIGHTING_SUPPORTED_THING_TYPES, AUTOMATION_SUPPORTED_THING_TYPES,
THERMOREGULATION_SUPPORTED_THING_TYPES, ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES,
- GENERIC_SUPPORTED_THING_TYPES)
+ SCENARIO_SUPPORTED_THING_TYPES, GENERIC_SUPPORTED_THING_TYPES)
.flatMap(Collection::stream).collect(Collectors.toCollection(HashSet::new));
-
public static final Set<ThingTypeUID> BRIDGE_SUPPORTED_THING_TYPES = Set.of(THING_TYPE_ZB_GATEWAY,
THING_TYPE_BUS_GATEWAY);
-
public static final Set<ThingTypeUID> ALL_SUPPORTED_THING_TYPES = Stream
.of(DEVICE_SUPPORTED_THING_TYPES, BRIDGE_SUPPORTED_THING_TYPES).flatMap(Collection::stream)
.collect(Collectors.toCollection(HashSet::new));
public static final String CHANNEL_SWITCH_01 = "switch_01";
public static final String CHANNEL_SWITCH_02 = "switch_02";
public static final String CHANNEL_BRIGHTNESS = "brightness";
-
// automation
public static final String CHANNEL_SHUTTER = "shutter";
-
// thermo
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_FUNCTION = "function";
public static final String CHANNEL_CONDITIONING_VALVES = "conditioningValves";
public static final String CHANNEL_HEATING_VALVES = "heatingValves";
public static final String CHANNEL_ACTUATORS = "actuators";
-
// energy management
public static final String CHANNEL_POWER = "power";
+ // scenario button channels
+ public static final String CHANNEL_SCENARIO_BUTTON = "button#";
+ public static final String CHANNEL_TYPE_CEN_BUTTON_EVENT = "cenButtonEvent";
+ public static final String CHANNEL_TYPE_CEN_PLUS_BUTTON_EVENT = "cenPlusButtonEvent";
// devices config properties
public static final String CONFIG_PROPERTY_WHERE = "where";
public static final String CONFIG_PROPERTY_SHUTTER_RUN = "shutterRun";
-
+ public static final String CONFIG_PROPERTY_SCENARIO_BUTTONS = "buttons";
// BUS gw config properties
public static final String CONFIG_PROPERTY_HOST = "host";
public static final String CONFIG_PROPERTY_SERIAL_PORT = "serialPort";
-
// properties
public static final String PROPERTY_OWNID = "ownId";
public static final String PROPERTY_ZIGBEEID = "zigbeeid";
import org.openhab.binding.openwebnet.internal.handler.OpenWebNetEnergyHandler;
import org.openhab.binding.openwebnet.internal.handler.OpenWebNetGenericHandler;
import org.openhab.binding.openwebnet.internal.handler.OpenWebNetLightingHandler;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetScenarioHandler;
import org.openhab.binding.openwebnet.internal.handler.OpenWebNetThermoregulationHandler;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
} else if (OpenWebNetThermoregulationHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
logger.debug("creating NEW THERMO Handler");
return new OpenWebNetThermoregulationHandler(thing);
+ } else if (OpenWebNetScenarioHandler.SUPPORTED_THING_TYPES.contains(thing.getThingTypeUID())) {
+ logger.debug("creating NEW SCENARIO Handler");
+ return new OpenWebNetScenarioHandler(thing);
}
logger.warn("ThingType {} is not supported by this binding", thing.getThingTypeUID());
return null;
--- /dev/null
+/**
+ * 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.openwebnet.internal.actions;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.handler.OpenWebNetScenarioHandler;
+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.communication.OWNException;
+import org.openwebnet4j.communication.Response;
+import org.openwebnet4j.message.CEN;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetCENActions} defines CEN/CEN+ actions for the openwebnet binding.
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+
+@ThingActionsScope(name = "openwebnet")
+@NonNullByDefault
+public class OpenWebNetCENActions implements ThingActions {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetCENActions.class);
+ private @Nullable OpenWebNetScenarioHandler scenarioHandler;
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ this.scenarioHandler = (OpenWebNetScenarioHandler) handler;
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return scenarioHandler;
+ }
+
+ @RuleAction(label = "virtualPress", description = "Virtual press of the push button")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean virtualPress(
+ @ActionInput(name = "press", label = "press", description = "Type of press") @Nullable String press,
+ @ActionInput(name = "button", label = "button", description = "Button number") int button) {
+ OpenWebNetScenarioHandler handler = scenarioHandler;
+ if (handler == null) {
+ logger.warn("openwebnet OpenWebNetCENActions: scenarioHandler is null!");
+ return false;
+ }
+ if (press == null) {
+ logger.warn("openwebnet OpenWebNetCENActions: press parameter is null!");
+ return false;
+ }
+ CEN msg = null;
+ try {
+ msg = handler.pressStrToMessage(press, button);
+ Response res = handler.send(msg);
+ if (res != null) {
+ logger.debug("Sent virtualPress '{}' to gateway. Response: {}", msg, res.getResponseMessages());
+ return res.isSuccess();
+ } else {
+ logger.debug("virtual press action returned null response");
+ }
+ } catch (IllegalArgumentException e) {
+ logger.warn("cannot execute virtual press action for thing {}: {}", handler.getThing().getUID(),
+ e.getMessage());
+ } catch (OWNException e) {
+ logger.warn("exception while sending virtual press message '{}' to gateway: {}", msg, e.getMessage());
+ }
+ return false;
+ }
+
+ // legacy delegate methods
+ public static void virtualPress(@Nullable ThingActions actions, @Nullable String press, int button) {
+ if (actions instanceof OpenWebNetCENActions) {
+ ((OpenWebNetCENActions) actions).virtualPress(press, button);
+ } else {
+ throw new IllegalArgumentException("Instance is not an OpenWebNetCENActions class.");
+ }
+ }
+}
deviceWho = Who.ENERGY_MANAGEMENT;
break;
}
+ case SCENARIO_CONTROL: {
+ thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_CEN_SCENARIO_CONTROL;
+ thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_CEN_SCENARIO_CONTROL;
+ deviceWho = Who.CEN_SCENARIO_SCHEDULER;
+ break;
+ }
+ case MULTIFUNCTION_SCENARIO_CONTROL: {
+ thingTypeUID = OpenWebNetBindingConstants.THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL;
+ thingLabel = OpenWebNetBindingConstants.THING_LABEL_BUS_CENPLUS_SCENARIO_CONTROL;
+ deviceWho = Who.CEN_PLUS_SCENARIO_SCHEDULER;
+ break;
+ }
default:
logger.warn("Device type {} is not supported, default to GENERIC device (WHERE={})", deviceType, where);
if (where instanceof WhereZigBee) {
import org.openwebnet4j.communication.OWNException;
import org.openwebnet4j.message.Automation;
import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.CEN;
import org.openwebnet4j.message.EnergyManagement;
import org.openwebnet4j.message.FrameException;
import org.openwebnet4j.message.GatewayMgmt;
}
// we support these types only
if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
- || baseMsg instanceof Thermoregulation) {
+ || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN) {
BaseOpenMessage bmsg = baseMsg;
if (baseMsg instanceof Lighting) {
What what = baseMsg.getWhat();
BaseOpenMessage baseMsg = (BaseOpenMessage) msg;
// let's try to get the Thing associated with this message...
if (baseMsg instanceof Lighting || baseMsg instanceof Automation || baseMsg instanceof EnergyManagement
- || baseMsg instanceof Thermoregulation) {
+ || baseMsg instanceof Thermoregulation || baseMsg instanceof CEN) {
String ownId = ownIdFromMessage(baseMsg);
logger.debug("ownIdFromMessage({}) --> {}", baseMsg, ownId);
OpenWebNetThingHandler deviceHandler = registeredDevices.get(ownId);
"subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
ENERGY_SUBSCRIPTION_PERIOD, deviceWhere);
}
-
- try {
- bridgeHandler.gateway.send(EnergyManagement.setActivePowerNotificationsTime(deviceWhere.value(),
- ENERGY_SUBSCRIPTION_PERIOD));
- isFirstSchedulerLaunch = false;
- } catch (Exception e) {
- if (isFirstSchedulerLaunch) {
- logger.warn(
- "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
- deviceWhere, e.getMessage());
- } else {
- logger.warn(
- "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
- deviceWhere, e.getMessage());
+ Where w = deviceWhere;
+ if (w == null) {
+ logger.warn("subscribeToActivePowerChanges() WHERE=null. Skipping");
+ } else {
+ try {
+ send(EnergyManagement.setActivePowerNotificationsTime(w.value(), ENERGY_SUBSCRIPTION_PERIOD));
+ isFirstSchedulerLaunch = false;
+ } catch (Exception e) {
+ if (isFirstSchedulerLaunch) {
+ logger.warn(
+ "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
+ w, e.getMessage());
+ } else {
+ logger.warn(
+ "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
+ w, e.getMessage());
+ }
}
}
}, 0, ENERGY_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
@Override
public void dispose() {
if (notificationSchedule != null) {
+ ScheduledFuture<?> ns = notificationSchedule;
+ ns.cancel(false);
logger.debug("dispose() scheduler stopped.");
-
- notificationSchedule.cancel(false);
}
super.dispose();
}
--- /dev/null
+/**
+ * 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.openwebnet.internal.handler;
+
+import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
+import org.openhab.binding.openwebnet.internal.actions.OpenWebNetCENActions;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.thing.type.ChannelKind;
+import org.openhab.core.thing.type.ChannelTypeUID;
+import org.openhab.core.types.Command;
+import org.openwebnet4j.message.BaseOpenMessage;
+import org.openwebnet4j.message.CEN;
+import org.openwebnet4j.message.CEN.Pressure;
+import org.openwebnet4j.message.CENPlusScenario;
+import org.openwebnet4j.message.CENPlusScenario.CENPlusPressure;
+import org.openwebnet4j.message.CENScenario;
+import org.openwebnet4j.message.CENScenario.CENPressure;
+import org.openwebnet4j.message.FrameException;
+import org.openwebnet4j.message.Where;
+import org.openwebnet4j.message.WhereCEN;
+import org.openwebnet4j.message.Who;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link OpenWebNetScenarioHandler} is responsible for handling commands/messages for CEN/CEN+ Scenarios. It
+ * extends the abstract {@link OpenWebNetThingHandler}.
+ *
+ * @author Massimo Valla - Initial contribution
+ */
+@NonNullByDefault
+public class OpenWebNetScenarioHandler extends OpenWebNetThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(OpenWebNetScenarioHandler.class);
+
+ private interface PressEvent {
+ @Override
+ public String toString();
+ }
+
+ private enum CENPressEvent implements PressEvent {
+ CEN_EVENT_START_PRESS("START_PRESS"),
+ CEN_EVENT_SHORT_PRESS("SHORT_PRESS"),
+ CEN_EVENT_EXTENDED_PRESS("EXTENDED_PRESS"),
+ CEN_EVENT_RELEASE_EXTENDED_PRESS("RELEASE_EXTENDED_PRESS");
+
+ private final String press;
+
+ CENPressEvent(final String pr) {
+ this.press = pr;
+ }
+
+ public static @Nullable CENPressEvent fromValue(String s) {
+ Optional<CENPressEvent> event = Arrays.stream(values()).filter(val -> s.equals(val.press)).findFirst();
+ return event.orElse(null);
+ }
+
+ @Override
+ public String toString() {
+ return press;
+ }
+ }
+
+ private enum CENPlusPressEvent implements PressEvent {
+ CENPLUS_EVENT_SHORT_PRESS("SHORT_PRESS"),
+ CENPLUS_EVENT_START_EXTENDED_PRESS("START_EXTENDED_PRESS"),
+ CENPLUS_EVENT_EXTENDED_PRESS("EXTENDED_PRESS"),
+ CENPLUS_EVENT_RELEASE_EXTENDED_PRESS("RELEASE_EXTENDED_PRESS");
+
+ private final String press;
+
+ CENPlusPressEvent(final String pr) {
+ this.press = pr;
+ }
+
+ public static @Nullable CENPlusPressEvent fromValue(String s) {
+ Optional<CENPlusPressEvent> event = Arrays.stream(values()).filter(val -> s.equals(val.press)).findFirst();
+ return event.orElse(null);
+ }
+
+ @Override
+ public String toString() {
+ return press;
+ }
+ }
+
+ private boolean isCENPlus = false;
+
+ public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.SCENARIO_SUPPORTED_THING_TYPES;
+
+ public OpenWebNetScenarioHandler(Thing thing) {
+ super(thing);
+ if (OpenWebNetBindingConstants.THING_TYPE_BUS_CENPLUS_SCENARIO_CONTROL.equals(thing.getThingTypeUID())) {
+ isCENPlus = true;
+ logger.debug("created CEN+ device for thing: {}", getThing().getUID());
+ } else {
+ logger.debug("created CEN device for thing: {}", getThing().getUID());
+ }
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ Object buttonsConfig = getConfig().get(CONFIG_PROPERTY_SCENARIO_BUTTONS);
+ if (buttonsConfig != null) {
+ Set<Integer> buttons = csvStringToSetInt((String) buttonsConfig);
+ if (!buttons.isEmpty()) {
+ ThingBuilder thingBuilder = editThing();
+ Channel ch;
+ for (Integer i : buttons) {
+ ch = thing.getChannel(CHANNEL_SCENARIO_BUTTON + i);
+ if (ch == null) {
+ thingBuilder.withChannel(buttonToChannel(i));
+ logger.debug("added channel {} to thing: {}", i, getThing().getUID());
+ }
+ }
+ updateThing(thingBuilder.build());
+ } else {
+ logger.warn("invalid config parameter buttons='{}' for thing {}", buttonsConfig, thing.getUID());
+ }
+ }
+ }
+
+ @Override
+ public Collection<Class<? extends ThingHandlerService>> getServices() {
+ return Collections.singleton(OpenWebNetCENActions.class);
+ }
+
+ @Override
+ protected String ownIdPrefix() {
+ if (isCENPlus) {
+ return Who.CEN_PLUS_SCENARIO_SCHEDULER.value().toString();
+ } else {
+ return Who.CEN_SCENARIO_SCHEDULER.value().toString();
+ }
+ }
+
+ @Override
+ protected void handleMessage(BaseOpenMessage msg) {
+ super.handleMessage(msg);
+ if (msg.isCommand()) {
+ triggerChannel((CEN) msg);
+ } else {
+ logger.debug("handleMessage() Ignoring unsupported DIM for thing {}. Frame={}", getThing().getUID(), msg);
+ }
+ }
+
+ private void triggerChannel(CEN cenMsg) {
+ Integer buttonNumber;
+ try {
+ buttonNumber = cenMsg.getButtonNumber();
+ } catch (FrameException e) {
+ logger.warn("cannot read CEN/CEN+ button. Ignoring message {}", cenMsg);
+ return;
+ }
+ if (buttonNumber == null || buttonNumber < 0 || buttonNumber > 31) {
+ logger.warn("invalid CEN/CEN+ button number: {}. Ignoring message {}", buttonNumber, cenMsg);
+ return;
+ }
+ Channel ch = thing.getChannel(CHANNEL_SCENARIO_BUTTON + buttonNumber);
+ if (ch == null) { // we have found a new button for this device, let's add a new channel for the button
+ ThingBuilder thingBuilder = editThing();
+ ch = buttonToChannel(buttonNumber);
+ thingBuilder.withChannel(ch);
+ updateThing(thingBuilder.build());
+ logger.info("added new channel {} to thing {}", ch.getUID(), getThing().getUID());
+ }
+ final Channel channel = ch;
+ PressEvent pressEv = null;
+ Pressure press = null;
+ try {
+ press = cenMsg.getButtonPressure();
+ } catch (FrameException e) {
+ logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
+ return;
+ }
+ if (press == null) {
+ logger.warn("invalid CEN/CEN+ Press. Ignoring message {}", cenMsg);
+ return;
+ }
+
+ if (cenMsg instanceof CENScenario) {
+ switch ((CENPressure) press) {
+ case START_PRESSURE:
+ pressEv = CENPressEvent.CEN_EVENT_START_PRESS;
+ break;
+ case RELEASE_SHORT_PRESSURE:
+ pressEv = CENPressEvent.CEN_EVENT_SHORT_PRESS;
+ break;
+ case EXTENDED_PRESSURE:
+ pressEv = CENPressEvent.CEN_EVENT_EXTENDED_PRESS;
+ break;
+ case RELEASE_EXTENDED_PRESSURE:
+ pressEv = CENPressEvent.CEN_EVENT_RELEASE_EXTENDED_PRESS;
+ break;
+ default:
+ logger.warn("unsupported CENPress. Ignoring message {}", cenMsg);
+ return;
+ }
+ } else {
+ switch ((CENPlusPressure) press) {
+ case SHORT_PRESSURE:
+ pressEv = CENPlusPressEvent.CENPLUS_EVENT_SHORT_PRESS;
+ break;
+ case START_EXTENDED_PRESSURE:
+ pressEv = CENPlusPressEvent.CENPLUS_EVENT_START_EXTENDED_PRESS;
+ break;
+ case EXTENDED_PRESSURE:
+ pressEv = CENPlusPressEvent.CENPLUS_EVENT_EXTENDED_PRESS;
+ break;
+ case RELEASE_EXTENDED_PRESSURE:
+ pressEv = CENPlusPressEvent.CENPLUS_EVENT_RELEASE_EXTENDED_PRESS;
+ break;
+ default:
+ logger.warn("unsupported CENPlusPress. Ignoring message {}", cenMsg);
+ return;
+ }
+ }
+
+ triggerChannel(channel.getUID(), pressEv.toString());
+ }
+
+ private Channel buttonToChannel(int buttonNumber) {
+ ChannelTypeUID channelTypeUID;
+ if (isCENPlus) {
+ channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_PLUS_BUTTON_EVENT);
+ } else {
+ channelTypeUID = new ChannelTypeUID(BINDING_ID, CHANNEL_TYPE_CEN_BUTTON_EVENT);
+ }
+ return ChannelBuilder
+ .create(new ChannelUID(getThing().getUID(), CHANNEL_SCENARIO_BUTTON + buttonNumber), "String")
+ .withType(channelTypeUID).withKind(ChannelKind.TRIGGER).withLabel("Button " + buttonNumber).build();
+ }
+
+ /**
+ * Construct a CEN/CEN+ virtual press message for this device given a pressString and button number
+ *
+ * @param pressString one START_PRESS, SHORT_PRESS etc.
+ * @param button number [0-31]
+ * @return CEN message
+ * @throws IllegalArgumentException if button number or pressString are invalid
+ */
+ public CEN pressStrToMessage(String pressString, int button) throws IllegalArgumentException {
+ Where w = deviceWhere;
+ if (w == null) {
+ throw new IllegalArgumentException("pressStrToMessage: deviceWhere is null");
+ }
+ if (isCENPlus) {
+ CENPlusPressEvent prEvent = CENPlusPressEvent.fromValue(pressString);
+ if (prEvent != null) {
+ switch (prEvent) {
+ case CENPLUS_EVENT_SHORT_PRESS:
+ return CENPlusScenario.virtualShortPressure(w.value(), button);
+ case CENPLUS_EVENT_START_EXTENDED_PRESS:
+ return CENPlusScenario.virtualStartExtendedPressure(w.value(), button);
+ case CENPLUS_EVENT_EXTENDED_PRESS:
+ return CENPlusScenario.virtualExtendedPressure(w.value(), button);
+ case CENPLUS_EVENT_RELEASE_EXTENDED_PRESS:
+ return CENPlusScenario.virtualReleaseExtendedPressure(w.value(), button);
+ default:
+ throw new IllegalArgumentException("unsupported press type: " + pressString);
+ }
+ } else {
+ throw new IllegalArgumentException("unsupported press type: " + pressString);
+ }
+ } else {
+ CENPressEvent prEvent = CENPressEvent.fromValue(pressString);
+ if (prEvent != null) {
+ switch (prEvent) {
+ case CEN_EVENT_START_PRESS:
+ return CENScenario.virtualStartPressure(w.value(), button);
+ case CEN_EVENT_SHORT_PRESS:
+ return CENScenario.virtualReleaseShortPressure(w.value(), button);
+ case CEN_EVENT_EXTENDED_PRESS:
+ return CENScenario.virtualExtendedPressure(w.value(), button);
+ case CEN_EVENT_RELEASE_EXTENDED_PRESS:
+ return CENScenario.virtualReleaseExtendedPressure(w.value(), button);
+ default:
+ throw new IllegalArgumentException("unsupported press type: " + pressString);
+ }
+ } else {
+ throw new IllegalArgumentException("unsupported press type: " + pressString);
+ }
+ }
+ }
+
+ private static Set<Integer> csvStringToSetInt(String s) {
+ TreeSet<Integer> intSet = new TreeSet<Integer>();
+ String sNorm = s.replaceAll("\\s", "");
+ Scanner sc = new Scanner(sNorm);
+ sc.useDelimiter(",");
+ while (sc.hasNextInt()) {
+ intSet.add(sc.nextInt());
+ }
+ sc.close();
+ return intSet;
+ }
+
+ @Override
+ protected void handleChannelCommand(ChannelUID channel, Command command) {
+ logger.warn("CEN/CEN+ channels are trigger channels and do not handle commands");
+ }
+
+ @Override
+ protected void refreshDevice(boolean refreshAll) {
+ logger.debug("CEN/CEN+ channels are trigger channels and do not have state");
+ }
+
+ @Override
+ protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
+ return new WhereCEN(wStr);
+ }
+
+ @Override
+ protected void requestChannelState(ChannelUID channel) {
+ logger.debug("CEN/CEN+ channels are trigger channels and do not have state");
+ }
+}
/**
* Helper method to send OWN messages from ThingHandlers
*/
- protected @Nullable Response send(OpenMessage msg) throws OWNException {
+ public @Nullable Response send(OpenMessage msg) throws OWNException {
OpenWebNetBridgeHandler bh = bridgeHandler;
if (bh != null) {
OpenGateway gw = bh.gateway;
</parameter>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: A/PL address: A=1 PL=3 --> where=13. On local bus: where=13#4#01</description>
</parameter>
</config-description>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="openwebnet"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Thing for BUS CEN+ Scenario Control (BTicino HC/HD/HS/L/N/NT4680) -->
+ <thing-type id="bus_cenplus_scenario_control">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bus_gateway"/>
+ </supported-bridge-type-refs>
+ <label>CEN+ Scenario Control</label>
+ <description>A OpenWebNet BUS/SCS CEN+ Scenario Control device. BTicino models: HC/HD/HS/L/N/NT4680</description>
+
+ <!-- channels are created dynamically based on configured buttons -->
+
+ <properties>
+ <property name="vendor">BTicino/Legrand</property>
+ <property name="model">BTI-HC/HD/HS/L/N/NT4680</property>
+ <property name="ownDeviceType">273</property>
+ </properties>
+
+ <representation-property>ownId</representation-property>
+
+ <config-description>
+ <parameter name="buttons" type="text">
+ <label>Configured Buttons</label>
+ <description>List (comma separated) of buttons numbers [0-31] configured for this scenario device, example:
+ buttons=1,2,4
+ </description>
+ </parameter>
+ <parameter name="where" type="text" required="true">
+ <label>OpenWebNet Address (where)</label>
+ <description>Use 2+N[0-2047]. Example: scenario control 5 --> WHERE=25</description>
+ </parameter>
+ </config-description>
+
+ </thing-type>
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="openwebnet"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Thing for BUS CEN Scenario Control (BTicino HC/HD/HS/L/N/NT4680) -->
+ <thing-type id="bus_cen_scenario_control">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="bus_gateway"/>
+ </supported-bridge-type-refs>
+
+ <label>CEN Scenario Control</label>
+ <description>A OpenWebNet BUS/SCS CEN Scenario Control device. BTicino models: HC/HD/HS/L/N/NT4680</description>
+
+ <!-- channels are created dynamically based on configured buttons -->
+
+ <properties>
+ <property name="vendor">BTicino/Legrand</property>
+ <property name="model">BTI-HC/HD/HS/L/N/NT4680</property>
+ <property name="ownDeviceType">2</property>
+ </properties>
+
+ <representation-property>ownId</representation-property>
+
+ <config-description>
+ <parameter name="buttons" type="text">
+ <label>Configured Buttons</label>
+ <description>List (comma separated) of buttons numbers [0-31] configured for this scenario device. Example:
+ buttons=1,2,4</description>
+ </parameter>
+ <parameter name="where" type="text" required="true">
+ <label>OpenWebNet Address (where)</label>
+ <description>Example: A/PL address: A=1 PL=3 --> WHERE=13. On local bus: WHERE=13#4#01</description>
+ </parameter>
+
+ </config-description>
+
+ </thing-type>
+</thing:thing-descriptions>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: A/PL address: A=1 PL=3 --> where=13. On local bus: where=13#4#01</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Address</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: 5N with N=[1-255]</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: A/PL address: A=1 PL=3 --> where=13. On local bus: where=13#4#01</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: sensor 3 of zone 2 --> where=302. Sensor 5 of external zone 00 --> where=500</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address</label>
+ <label>OpenWebNet Address (where)</label>
<description>Example: zone 2 --> where=2.</description>
</parameter>
<default>AUTO</default>
</parameter>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>It identifies one ZigBee device. Example: 765432101#9</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>It identifies one ZigBee device. Example: 765432101#9</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>It identifies one ZigBee device. Example: 765432101#9</description>
</parameter>
</config-description>
<config-description>
<parameter name="where" type="text" required="true">
- <label>OpenWebNet Device Address (where)</label>
+ <label>OpenWebNet Address (where)</label>
<description>It identifies one ZigBee device. Example: 765432100#9 (use unit=00 at the end)</description>
</parameter>
</config-description>
<category>Energy</category>
<state readOnly="true" pattern="%.0f %unit%"></state>
</channel-type>
+
+ <!-- CEN/CEN+ trigger channels -->
+ <channel-type id="cenButtonEvent">
+ <kind>trigger</kind>
+ <label>CEN Button Event</label>
+ <event>
+ <options>
+ <option value="START_PRESS">start press</option>
+ <option value="SHORT_PRESS">release after short press</option>
+ <option value="EXTENDED_PRESS">extended press (repeated until release)</option>
+ <option value="RELEASE_EXTENDED_PRESS">release after extended press</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="cenPlusButtonEvent">
+ <kind>trigger</kind>
+ <label>CEN+ Button Event</label>
+ <event>
+ <options>
+ <option value="SHORT_PRESS">short press</option>
+ <option value="START_EXTENDED_PRESS">start of extended press</option>
+ <option value="EXTENDED_PRESS">extended press (repeated until release)</option>
+ <option value="RELEASE_EXTENDED_PRESS">release after extended press</option>
+ </options>
+ </event>
+ </channel-type>
</thing:thing-descriptions>