]> git.basschouten.com Git - openhab-addons.git/blob
7557429fcae085068949425dffe81cf711171bcc
[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 javax.measure.quantity.Energy;
24 import javax.measure.quantity.Power;
25
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;
49
50 /**
51  * The {@link OpenWebNetEnergyHandler} is responsible for handling commands/messages for an Energy Management OpenWebNet
52  * device. It extends the abstract {@link OpenWebNetThingHandler}.
53  *
54  * @author Massimo Valla - Initial contribution
55  * @author Andrea Conte, Giovanni Fabiani - Energy management
56  */
57 @NonNullByDefault
58 public class OpenWebNetEnergyHandler extends OpenWebNetThingHandler {
59
60     private final Logger logger = LoggerFactory.getLogger(OpenWebNetEnergyHandler.class);
61
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
65
66     private @Nullable ScheduledFuture<?> powerSchedule;
67     private @Nullable ScheduledFuture<?> energySchedule;
68
69     public OpenWebNetEnergyHandler(Thing thing) {
70         super(thing);
71     }
72
73     public Boolean isFirstSchedulerLaunch = true;
74
75     @Override
76     public void initialize() {
77         super.initialize();
78         try {
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: {}",
83                     e.getMessage());
84             energyRefreshPeriod = 30;
85         }
86
87         // In order to get data from the probe we must send a command over the bus, this could be done only when the
88         // bridge is online.
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.
93
94         OpenWebNetBridgeHandler h = bridgeHandler;
95         if (h != null && h.isBusGateway()) {
96             OpenGateway gw = h.gateway;
97             if (gw != null && gw.isConnected()) {
98                 // bridge is online
99                 subscribeToActivePowerChanges();
100                 subscribeToEnergyTotalizer();
101             }
102         }
103     }
104
105     @Override
106     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
107         super.bridgeStatusChanged(bridgeStatusInfo);
108
109         // subscribe the scheduler only after the bridge is online
110         if (bridgeStatusInfo.getStatus().equals(ThingStatus.ONLINE)) {
111             subscribeToActivePowerChanges();
112             subscribeToEnergyTotalizer();
113         }
114     }
115
116     private void subscribeToActivePowerChanges() {
117         powerSchedule = scheduler.scheduleWithFixedDelay(() -> {
118             if (isFirstSchedulerLaunch) {
119                 logger.debug(
120                         "subscribeToActivePowerChanges() For WHERE={} subscribing to active power changes notification for the next {}min",
121                         deviceWhere, POWER_SUBSCRIPTION_PERIOD);
122             } else {
123                 logger.debug(
124                         "subscribeToActivePowerChanges() Refreshing subscription for the next {}min for WHERE={} to active power changes notification",
125                         POWER_SUBSCRIPTION_PERIOD, deviceWhere);
126             }
127             Where w = deviceWhere;
128             if (w == null) {
129                 logger.warn("subscribeToActivePowerChanges() WHERE=null. Skipping");
130             } else {
131                 try {
132                     send(EnergyManagement.setActivePowerNotificationsTime(w.value(), POWER_SUBSCRIPTION_PERIOD));
133                     isFirstSchedulerLaunch = false;
134                 } catch (Exception e) {
135                     if (isFirstSchedulerLaunch) {
136                         logger.warn(
137                                 "subscribeToActivePowerChanges() For WHERE={} could not subscribe to active power changes notifications. Exception={}",
138                                 w, e.getMessage());
139                     } else {
140                         logger.warn(
141                                 "subscribeToActivePowerChanges() Unable to refresh subscription to active power changes notifications for WHERE={}. Exception={}",
142                                 w, e.getMessage());
143                     }
144                 }
145             }
146         }, 0, POWER_SUBSCRIPTION_PERIOD - 1, TimeUnit.MINUTES);
147     }
148
149     private void subscribeToEnergyTotalizer() {
150         Where w = deviceWhere;
151         if (w == null) {
152             logger.warn("subscribeToEnergyTotalizer() WHERE=null. Skipping");
153             return;
154         }
155         energySchedule = scheduler.scheduleWithFixedDelay(() -> {
156             try {
157                 send(EnergyManagement.requestCurrentDayTotalizer(w.value()));
158                 send(EnergyManagement.requestCurrentMonthTotalizer(w.value()));
159             } catch (Exception e) {
160                 logger.warn(
161                         "subscribeToEnergyTotalizer() Could not subscribe to totalizers scheduler for WHERE={}. Exception={}",
162                         w, e.getMessage());
163             }
164         }, 0, energyRefreshPeriod, TimeUnit.MINUTES);
165     }
166
167     @Override
168     public void dispose() {
169         ScheduledFuture<?> sfp = powerSchedule;
170         if (sfp != null) {
171             sfp.cancel(false);
172             powerSchedule = null;
173             logger.debug("dispose() power scheduler stopped.");
174         }
175         ScheduledFuture<?> sfe = energySchedule;
176         if (sfe != null) {
177             sfe.cancel(false);
178             energySchedule = null;
179             logger.debug("dispose() energy scheduler stopped.");
180         }
181         super.dispose();
182     }
183
184     @Override
185     protected Where buildBusWhere(String wStr) throws IllegalArgumentException {
186         return new WhereEnergyManagement(wStr);
187     }
188
189     @Override
190     protected void requestChannelState(ChannelUID channel) {
191         super.requestChannelState(channel);
192         Where w = deviceWhere;
193         if (w != null) {
194             try {
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());
201             }
202         }
203     }
204
205     @Override
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));
211     }
212
213     @Override
214     protected void handleChannelCommand(ChannelUID channel, Command command) {
215         logger.warn("handleChannelCommand() Read only channel, unsupported command {}", command);
216     }
217
218     @Override
219     protected String ownIdPrefix() {
220         return Who.ENERGY_MANAGEMENT.value().toString();
221     }
222
223     @Override
224     protected void handleMessage(BaseOpenMessage msg) {
225         super.handleMessage(msg);
226
227         if (msg.isCommand()) {
228             logger.warn("handleMessage() Ignoring unsupported command for thing {}. Frame={}", getThing().getUID(),
229                     msg);
230             return;
231         } else {
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);
239             } else {
240                 logger.debug("handleMessage() Ignoring message {} because it's not related to active power value.",
241                         msg);
242             }
243         }
244     }
245
246     /**
247      * Updates energy power state based on an EnergyManagement message received from the OWN network
248      *
249      * @param msg the EnergyManagement message received
250      */
251     private void updateActivePower(BaseOpenMessage msg) {
252         Integer activePower;
253         try {
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);
262         }
263     }
264
265     /**
266      * Updates current day totalizer
267      *
268      * @param msg the EnergyManagement message received
269      */
270     private void updateCurrentDayTotalizer(BaseOpenMessage msg) {
271         Double currentDayEnergy;
272         try {
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);
281         }
282     }
283
284     /**
285      * Updates current month totalizer
286      *
287      * @param msg the EnergyManagement message received
288      */
289     private void updateCurrentMonthTotalizer(BaseOpenMessage msg) {
290         Double currentMonthEnergy;
291         try {
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);
301         }
302     }
303 }