]> git.basschouten.com Git - openhab-addons.git/blob
2b34b98a483fdbf3c5bba951df99acfb897c23db
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.openwebnet.internal.handler;
14
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;
18
19 import java.util.Set;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.openwebnet.internal.OpenWebNetBindingConstants;
26 import org.openhab.core.library.types.QuantityType;
27 import org.openhab.core.library.unit.Units;
28 import org.openhab.core.thing.ChannelUID;
29 import org.openhab.core.thing.Thing;
30 import org.openhab.core.thing.ThingStatus;
31 import org.openhab.core.thing.ThingStatusDetail;
32 import org.openhab.core.thing.ThingStatusInfo;
33 import org.openhab.core.thing.ThingTypeUID;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.UnDefType;
36 import org.openwebnet4j.OpenGateway;
37 import org.openwebnet4j.communication.OWNException;
38 import org.openwebnet4j.message.BaseOpenMessage;
39 import org.openwebnet4j.message.EnergyManagement;
40 import org.openwebnet4j.message.FrameException;
41 import org.openwebnet4j.message.Where;
42 import org.openwebnet4j.message.WhereEnergyManagement;
43 import org.openwebnet4j.message.Who;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for an Energy Management OpenWebNet
49  * device. It extends the abstract {@link OpenWebNetThingHandler}.
50  *
51  * @author Massimo Valla - Initial contribution
52  * @author Andrea Conte, Giovanni Fabiani - Energy management
53  */
54 @NonNullByDefault
55 public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
56
57     private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
58
59     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = OpenWebNetBindingConstants.ENERGY_MANAGEMENT_SUPPORTED_THING_TYPES;
60     private static final int POWER_SUBSCRIPTION_PERIOD = 10; // MINUTES
61     private int energyRefreshPeriod; // MINUTES
62
63     private @Nullable ScheduledFuture<?> powerSchedule;
64     private @Nullable ScheduledFuture<?> energySchedule;
65
66     public OpenWebNetEnergyHandler(Thing thing) {
67         super(thing);
68     }
69
70     public Boolean isFirstSchedulerLaunch = true;
71
72     @Override
73     public void initialize() {
74         super.initialize();
75         try {
76             Object refreshPeriodConfig = getConfig().get(OpenWebNetBindingConstants.CONFIG_PROPERTY_REFRESH_PERIOD);
77             energyRefreshPeriod = Integer.parseInt(refreshPeriodConfig.toString());
78         } catch (NumberFormatException e) {
79             logger.debug("NumberFormatException caught while parsing OpenWebNetEnergyHandler configuration: {}",
80                     e.getMessage());
81             energyRefreshPeriod = 30;
82         }
83
84         // In order to get data from the probe we must send a command over the bus, this could be done only when the
85         // bridge is online.
86         // a) usual flow: binding is starting, the bridge isn't online (startup flow not yet completed) --> subscriber
87         // can't be started here, it will be started inside the bridgeStatusChanged event.
88         // b) thing's discovery: binding is up and running, the bridge is online --> subscriber must be started here
89         // otherwise data will be read only after a reboot.
90
91         OpenWebNetBridgeHandler h = bridgeHandler;
92         if (h != null && h.isBusGateway()) {
93             OpenGateway gw = h.gateway;
94             if (gw != null && gw.isConnected()) {
95                 // bridge is online
96                 subscribeToActivePowerChanges();
97                 subscribeToEnergyTotalizer();
98             }
99         }
100     }
101
102     @Override
103     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
104         super.bridgeStatusChanged(bridgeStatusInfo);
105
106         // subscribe the scheduler only after the bridge is online
107         if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
108             subscribeToActivePowerChanges();
109             subscribeToEnergyTotalizer();
110         }
111     }
112
113     private void subscribeToActivePowerChanges() {
114         powerSchedule = scheduler.scheduleWithFixedDelay(() -> {
115             if (isFirstSchedulerLaunch) {
116                 logger.debug(
117                         "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
118                         deviceWhere, POWER_SUBSCRIPTION_PERIOD);
119             } else {
120                 logger.debug(
121                         "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
122                         POWER_SUBSCRIPTION_PERIOD, deviceWhere);
123             }
124             Where w = deviceWhere;
125             if (w == null) {
126                 logger.warn("subscribeToActivePowerChanges() WHERE=null. Skipping");
127             } else {
128                 try {
129                     send(EnergyManagement.setActivePowerNotificationsTime(w.value(), POWER_SUBSCRIPTION_PERIOD));
130                     isFirstSchedulerLaunch = false;
131                 } catch (Exception e) {
132                     if (isFirstSchedulerLaunch) {
133                         logger.warn(
134                                 "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
135                                 w, e.getMessage());
136                     } else {
137                         logger.warn(
138                                 "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
139                                 w, e.getMessage());
140                     }
141                 }
142             }
143         }, 0, POWER_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
144     }
145
146     private void subscribeToEnergyTotalizer() {
147         Where w = deviceWhere;
148         if (w == null) {
149             logger.warn("subscribeToEnergyTotalizer() WHERE=null. Skipping");
150             return;
151         }
152         energySchedule = scheduler.scheduleWithFixedDelay(() -> {
153             try {
154                 send(EnergyManagement.requestCurrentDayTotalizer(w.value()));
155                 send(EnergyManagement.requestCurrentMonthTotalizer(w.value()));
156             } catch (Exception e) {
157                 logger.warn(
158                         "subscribeToEnergyTotalizer() Could not subscribe to totalizers scheduler for WHERE={}. Exception={}",
159                         w, e.getMessage());
160             }
161         }, 0, energyRefreshPeriod, TimeUnit.MINUTES);
162     }
163
164     @Override
165     public void dispose() {
166         ScheduledFuture<?> sfp = powerSchedule;
167         if (sfp != null) {
168             sfp.cancel(false);
169             powerSchedule = null;
170             logger.debug("dispose() power scheduler stopped.");
171         }
172         ScheduledFuture<?> sfe = energySchedule;
173         if (sfe != null) {
174             sfe.cancel(false);
175             energySchedule = null;
176             logger.debug("dispose() energy scheduler stopped.");
177         }
178         super.dispose();
179     }
180
181     @Override
182     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
183         return new WhereEnergyManagement(wStr);
184     }
185
186     @Override
187     protected void requestChannelState(ChannelUID channel) {
188         super.requestChannelState(channel);
189         Where w = deviceWhere;
190         if (w != null) {
191             try {
192                 send(EnergyManagement.requestActivePower(w.value()));
193                 send(EnergyManagement.requestCurrentDayTotalizer(w.value()));
194                 send(EnergyManagement.requestCurrentMonthTotalizer(w.value()));
195             } catch (OWNException e) {
196                 logger.debug("Exception while requesting state for channel {}: {} ", channel, e.getMessage());
197                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
198             }
199         }
200     }
201
202     @Override
203     protected void refreshDevice(boolean refreshAll) {
204         logger.debug("--- refreshDevice() : refreshing SINGLE... ({})", thing.getUID());
205         requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_POWER));
206         requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_DAY));
207         requestChannelState(new ChannelUID(thing.getUID(), CHANNEL_ENERGY_TOTALIZER_MONTH));
208     }
209
210     @Override
211     protected void handleChannelCommand(ChannelUID channel, Command command) {
212         logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
213     }
214
215     @Override
216     protected String ownIdPrefix() {
217         return Who.ENERGY_MANAGEMENT.value().toString();
218     }
219
220     @Override
221     protected void handleMessage(BaseOpenMessage msg) {
222         super.handleMessage(msg);
223
224         if (msg.isCommand()) {
225             logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
226                     msg);
227             return;
228         } else {
229             // fix: check for correct DIM (ActivePower / 113)
230             if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.ACTIVE_POWER)) {
231                 updateActivePower(msg);
232             } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_DAY)) {
233                 updateCurrentDayTotalizer(msg);
234             } else if (msg.getDim().equals(EnergyManagement.DimEnergyMgmt.PARTIAL_TOTALIZER_CURRENT_MONTH)) {
235                 updateCurrentMonthTotalizer(msg);
236             } else {
237                 logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
238                         msg);
239             }
240         }
241     }
242
243     /**
244      * Updates energy power state based on an EnergyManagement message received from the OWN network
245      *
246      * @param msg the EnergyManagement message received
247      */
248     private void updateActivePower(BaseOpenMessage msg) {
249         Integer activePower;
250         try {
251             activePower = Integer.parseInt(msg.getDimValues()[0]);
252             updateState(CHANNEL_POWER, new QuantityType<>(activePower, Units.WATT));
253         } catch (FrameException e) {
254             logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
255             updateState(CHANNEL_POWER, UnDefType.UNDEF);
256         } catch (NumberFormatException e) {
257             logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
258             updateState(CHANNEL_POWER, UnDefType.UNDEF);
259         }
260     }
261
262     /**
263      * Updates current day totalizer
264      *
265      * @param msg the EnergyManagement message received
266      */
267     private void updateCurrentDayTotalizer(BaseOpenMessage msg) {
268         Double currentDayEnergy;
269         try {
270             currentDayEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d;
271             updateState(CHANNEL_ENERGY_TOTALIZER_DAY, new QuantityType<>(currentDayEnergy, Units.KILOWATT_HOUR));
272         } catch (FrameException e) {
273             logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
274             updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF);
275         } catch (NumberFormatException e) {
276             logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
277             updateState(CHANNEL_ENERGY_TOTALIZER_DAY, UnDefType.UNDEF);
278         }
279     }
280
281     /**
282      * Updates current month totalizer
283      *
284      * @param msg the EnergyManagement message received
285      */
286     private void updateCurrentMonthTotalizer(BaseOpenMessage msg) {
287         Double currentMonthEnergy;
288         try {
289             currentMonthEnergy = Double.parseDouble(msg.getDimValues()[0]) / 1000d;
290             updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, new QuantityType<>(currentMonthEnergy, Units.KILOWATT_HOUR));
291         } catch (FrameException e) {
292             logger.warn("FrameException on frame {}: {}", msg, e.getMessage());
293             updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF);
294         } catch (NumberFormatException e) {
295             logger.warn("NumberFormatException on frame {}: {}", msg, e.getMessage());
296             updateState(CHANNEL_ENERGY_TOTALIZER_MONTH, UnDefType.UNDEF);
297         }
298     }
299 }