2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.openwebnet.internal.handler;
15 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_ENERGY_TOTALIZER_DAY;
16 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_ENERGY_TOTALIZER_MONTH;
17 import static org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants.CHANNEL_POWER;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import javax.measure.quantity.Energy;
24 import javax.measure.quantity.Power;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
29 import org.openhab.core.library.types.QuantityType;
30 import org.openhab.core.library.unit.Units;
31 import org.openhab.core.thing.ChannelUID;
32 import org.openhab.core.thing.Thing;
33 import org.openhab.core.thing.ThingStatus;
34 import org.openhab.core.thing.ThingStatusDetail;
35 import org.openhab.core.thing.ThingStatusInfo;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.types.Command;
38 import org.openhab.core.types.UnDefType;
39 import org.openwebnet4j.OpenGateway;
40 import org.openwebnet4j.communication.OWNException;
41 import org.openwebnet4j.message.BaseOpenMessage;
42 import org.openwebnet4j.message.EnergyManagement;
43 import org.openwebnet4j.message.FrameException;
44 import org.openwebnet4j.message.Where;
45 import org.openwebnet4j.message.WhereEnergyManagement;
46 import org.openwebnet4j.message.Who;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for an Energy Management OpenWebNet
52 * device. It extends the abstract {@link OpenWebNetThingHandler}.
54 * @author Massimo Valla - Initial contribution
55 * @author Andrea Conte, Giovanni Fabiani - Energy management
58 public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
60 private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
62 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
63 private static final int POWER_SUBSCRIPTION_PERIOD = 10; // MINUTES
64 private int energyRefreshPeriod; // MINUTES
66 private @Nullable ScheduledFuture<?> powerSchedule;
67 private @Nullable ScheduledFuture<?> energySchedule;
69 public OpenWebNetEnergyHandler(Thing thing) {
73 public Boolean isFirstSchedulerLaunch = true;
76 public void initialize() {
79 Object refreshPeriodConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_REFRESH_PERIOD);
80 energyRefreshPeriod = Integer.parseInt(refreshPeriodConfig.toString());
81 } catch (NumberFormatException e) {
82 logger.debug("NumberFormatException caught while parsing OpenWebNetEnergyHandler configuration: {}",
84 energyRefreshPeriod = 30;
87 // In order to get data from the probe we must send a command over the bus, this could be done only when the
89 // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
90 // can't be started here, it will be started inside the bridgeStatusChanged event.
91 // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
92 // otherwise data will be read only after a reboot.
94 OpenWebNetBridgeHandler h = bridgeHandler;
95 if (h != null && h.isBusGateway()) {
96 OpenGateway gw = h.gateway;
97 if (gw != null && gw.isConnected()) {
99 subscribeToActivePowerChanges();
100 subscribeToEnergyTotalizer();
106 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
107 super.bridgeStatusChanged(bridgeStatusInfo);
109 // subscribe the scheduler only after the bridge is online
110 if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
111 subscribeToActivePowerChanges();
112 subscribeToEnergyTotalizer();
116 private void subscribeToActivePowerChanges() {
117 powerSchedule = scheduler.scheduleWithFixedDelay(() -> {
118 if (isFirstSchedulerLaunch) {
120 "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
121 deviceWhere, POWER_SUBSCRIPTION_PERIOD);
124 "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
125 POWER_SUBSCRIPTION_PERIOD, deviceWhere);
127 Where w = deviceWhere;
129 logger.warn("subscribeToActivePowerChanges() WHERE=null. Skipping");
132 send(EnergyManagement.setActivePowerNotificationsTime(w.value(), POWER_SUBSCRIPTION_PERIOD));
133 isFirstSchedulerLaunch = false;
134 } catch (Exception e) {
135 if (isFirstSchedulerLaunch) {
137 "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
141 "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
146 }, 0, POWER_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
149 private void subscribeToEnergyTotalizer() {
150 Where w = deviceWhere;
152 logger.warn("subscribeToEnergyTotalizer() WHERE=null. Skipping");
155 energySchedule = scheduler.scheduleWithFixedDelay(() -> {
157 send(EnergyManagement.requestCurrentDayTotalizer(w.value()));
158 send(EnergyManagement.requestCurrentMonthTotalizer(w.value()));
159 } catch (Exception e) {
161 "subscribeToEnergyTotalizer() Could not subscribe to totalizers scheduler for WHERE={}. Exception={}",
164 }, 0, energyRefreshPeriod, TimeUnit.MINUTES);
168 public void dispose() {
169 ScheduledFuture<?> sfp = powerSchedule;
172 powerSchedule = null;
173 logger.debug("dispose() power scheduler stopped.");
175 ScheduledFuture<?> sfe = energySchedule;
178 energySchedule = null;
179 logger.debug("dispose() energy scheduler stopped.");
185 protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
186 return new WhereEnergyManagement(wStr);
190 protected void requestChannelState(ChannelUID channel) {
191 super.requestChannelState(channel);
192 Where w = deviceWhere;
195 send(EnergyManagement.requestActivePower(w.value()));
196 send(EnergyManagement.requestCurrentDayTotalizer(w.value()));
197 send(EnergyManagement.requestCurrentMonthTotalizer(w.value()));
198 } catch (OWNException e) {
199 logger.debug("Exception while requesting state for channel {}: {} ", channel, e.getMessage());
200 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
206 protected void refreshDevice(boolean refreshAll) {
207 logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
208 requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_POWER));
209 requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_DAY));
210 requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_MONTH));
214 protected void handleChannelCommand(ChannelUID channel, Command command) {
215 logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
219 protected String ownIdPrefix() {
220 return Who.ENERGY_MANAGEMENT.value().toString();
224 protected void handleMessage(BaseOpenMessage msg) {
225 super.handleMessage(msg);
227 if (msg.isCommand()) {
228 logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
232 // fix: check for correct DIM (ActivePower / 113)
233 if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
234 updateActivePower(msg);
235 } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_DAY)) {
236 updateCurrentDayTotalizer(msg);
237 } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_MONTH)) {
238 updateCurrentMonthTotalizer(msg);
240 logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
247 * Updates energy power state based on an EnergyManagement message received from the OWN network
249 * @param msg the EnergyManagement message received
251 private void updateActivePower(BaseOpenMessage msg) {
254 activePower = Integer.parseInt(msg.getDimValues()[0]);
255 updateState(CHANNEL_POWER, new QuantityType<Power>(activePower, Units.WATT));
256 } catch (FrameException e) {
257 logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
258 updateState(CHANNEL_POWER, UnDefType.UNDEF);
259 } catch (NumberFormatException e) {
260 logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
261 updateState(CHANNEL_POWER, UnDefType.UNDEF);
266 * Updates current day totalizer
268 * @param msg the EnergyManagement message received
270 private void updateCurrentDayTotalizer(BaseOpenMessage msg) {
271 Double currentDayEnergy;
273 currentDayEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d;
274 updateState(CHANNEL_ENERGY_TOTALIZER_DAY, new QuantityType<Energy>(currentDayEnergy, Units.KILOWATT_HOUR));
275 } catch (FrameException e) {
276 logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
277 updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF);
278 } catch (NumberFormatException e) {
279 logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
280 updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF);
285 * Updates current month totalizer
287 * @param msg the EnergyManagement message received
289 private void updateCurrentMonthTotalizer(BaseOpenMessage msg) {
290 Double currentMonthEnergy;
292 currentMonthEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d;
293 updateState(CHANNEL_ENERGY_TOTALIZER_MONTH,
294 new QuantityType<Energy>(currentMonthEnergy, Units.KILOWATT_HOUR));
295 } catch (FrameException e) {
296 logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
297 updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF);
298 } catch (NumberFormatException e) {
299 logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
300 updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF);