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.sonnen.internal;
15 import static org.openhab.binding.sonnen.internal.SonnenBindingConstants.*;
17 import java.util.HashMap;
19 import java.util.concurrent.ScheduledFuture;
20 import java.util.concurrent.TimeUnit;
22 import javax.measure.quantity.Dimensionless;
23 import javax.measure.quantity.Power;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.sonnen.internal.communication.SonnenJSONCommunication;
28 import org.openhab.binding.sonnen.internal.communication.SonnenJsonDataDTO;
29 import org.openhab.core.library.types.OnOffType;
30 import org.openhab.core.library.types.QuantityType;
31 import org.openhab.core.library.unit.Units;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.openhab.core.types.UnDefType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link SonnenHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Christian Feininger - Initial contribution
52 public class SonnenHandler extends BaseThingHandler {
54 private final Logger logger = LoggerFactory.getLogger(SonnenHandler.class);
56 private SonnenConfiguration config = new SonnenConfiguration();
58 private @Nullable ScheduledFuture<?> refreshJob;
60 private SonnenJSONCommunication serviceCommunication;
62 private boolean automaticRefreshing = false;
64 private Map<String, Boolean> linkedChannels = new HashMap<>();
66 public SonnenHandler(Thing thing) {
68 serviceCommunication = new SonnenJSONCommunication();
72 public void initialize() {
73 logger.debug("Initializing sonnen handler for thing {}", getThing().getUID());
74 config = getConfigAs(SonnenConfiguration.class);
75 if (config.refreshInterval < 0 || config.refreshInterval > 1000) {
76 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
77 "Parameter 'refresh Rate' msut be in the range 0-1000!");
80 if (config.hostIP == null) {
81 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP Address must be configured!");
85 serviceCommunication.setConfig(config);
86 updateStatus(ThingStatus.UNKNOWN);
87 scheduler.submit(() -> {
88 if (updateBatteryData()) {
89 for (Channel channel : getThing().getChannels()) {
90 if (isLinked(channel.getUID().getId())) {
91 channelLinked(channel.getUID());
99 * Calls the service to update the battery data
101 * @return true if the update succeeded, false otherwise
103 private boolean updateBatteryData() {
104 String error = serviceCommunication.refreshBatteryConnection();
105 if (error.isEmpty()) {
106 if (!ThingStatus.ONLINE.equals(getThing().getStatus())) {
107 updateStatus(ThingStatus.ONLINE);
110 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
112 return error.isEmpty();
115 private void verifyLinkedChannel(String channelID) {
116 if (isLinked(channelID) && !linkedChannels.containsKey(channelID)) {
117 linkedChannels.put(channelID, true);
122 public void dispose() {
123 stopAutomaticRefresh();
124 linkedChannels.clear();
125 automaticRefreshing = false;
128 private void stopAutomaticRefresh() {
129 ScheduledFuture<?> job = refreshJob;
137 * Start the job refreshing the oven status
139 private void startAutomaticRefresh() {
140 ScheduledFuture<?> job = refreshJob;
141 if (job == null || job.isCancelled()) {
142 refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, 0, config.refreshInterval,
147 private void refreshChannels() {
149 for (Channel channel : getThing().getChannels()) {
150 updateChannel(channel.getUID().getId());
155 public void channelLinked(ChannelUID channelUID) {
156 if (!automaticRefreshing) {
157 logger.debug("Start automatic refreshing");
158 startAutomaticRefresh();
159 automaticRefreshing = true;
161 verifyLinkedChannel(channelUID.getId());
162 updateChannel(channelUID.getId());
166 public void channelUnlinked(ChannelUID channelUID) {
167 linkedChannels.remove(channelUID.getId());
168 if (linkedChannels.isEmpty()) {
169 automaticRefreshing = false;
170 stopAutomaticRefresh();
171 logger.debug("Stop automatic refreshing");
175 private void updateChannel(String channelId) {
176 if (isLinked(channelId)) {
178 SonnenJsonDataDTO data = serviceCommunication.getBatteryData();
181 case CHANNELBATTERYDISCHARGINGSTATE:
182 update(OnOffType.from(data.isBatteryDischarging()), channelId);
184 case CHANNELBATTERYCHARGINGSTATE:
185 update(OnOffType.from(data.isBatteryCharging()), channelId);
187 case CHANNELCONSUMPTION:
188 state = new QuantityType<Power>(data.getConsumptionHouse(), Units.WATT);
189 update(state, channelId);
191 case CHANNELBATTERYDISCHARGING:
192 state = new QuantityType<Power>(data.getbatteryCurrent() > 0 ? data.getbatteryCurrent() : 0,
194 update(state, channelId);
196 case CHANNELBATTERYCHARGING:
197 state = new QuantityType<Power>(
198 data.getbatteryCurrent() <= 0 ? (data.getbatteryCurrent() * -1) : 0, Units.WATT);
199 update(state, channelId);
201 case CHANNELGRIDFEEDIN:
202 state = new QuantityType<Power>(data.getGridValue() > 0 ? data.getGridValue() : 0, Units.WATT);
203 update(state, channelId);
205 case CHANNELGRIDCONSUMPTION:
206 state = new QuantityType<Power>(data.getGridValue() <= 0 ? (data.getGridValue() * -1) : 0,
208 update(state, channelId);
210 case CHANNELSOLARPRODUCTION:
211 state = new QuantityType<Power>(data.getSolarProduction(), Units.WATT);
212 update(state, channelId);
214 case CHANNELBATTERYLEVEL:
215 state = new QuantityType<Dimensionless>(data.getBatteryChargingLevel(), Units.PERCENT);
216 update(state, channelId);
218 case CHANNELFLOWCONSUMPTIONBATTERYSTATE:
219 update(OnOffType.from(data.isFlowConsumptionBattery()), channelId);
221 case CHANNELFLOWCONSUMPTIONGRIDSTATE:
222 update(OnOffType.from(data.isFlowConsumptionGrid()), channelId);
224 case CHANNELFLOWCONSUMPTIONPRODUCTIONSTATE:
225 update(OnOffType.from(data.isFlowConsumptionProduction()), channelId);
227 case CHANNELFLOWGRIDBATTERYSTATE:
228 update(OnOffType.from(data.isFlowGridBattery()), channelId);
230 case CHANNELFLOWPRODUCTIONBATTERYSTATE:
231 update(OnOffType.from(data.isFlowProductionBattery()), channelId);
233 case CHANNELFLOWPRODUCTIONGRIDSTATE:
234 update(OnOffType.from(data.isFlowProductionGrid()), channelId);
238 update(null, channelId);
244 * Updates the State of the given channel
246 * @param state Given state
247 * @param channelId the refereed channelID
249 private void update(@Nullable State state, String channelId) {
250 logger.debug("Update channel {} with state {}", channelId, (state == null) ? "null" : state.toString());
251 updateState(channelId, state != null ? state : UnDefType.UNDEF);
255 public void handleCommand(ChannelUID channelUID, Command command) {
256 if (command == RefreshType.REFRESH) {
258 updateChannel(channelUID.getId());