]> git.basschouten.com Git - openhab-addons.git/blob
241599ee148cf303ff71a9bf35d80b55c3f846ef
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.tibber.internal.handler;
14
15 import static org.openhab.binding.tibber.internal.TibberBindingConstants.*;
16
17 import java.io.IOException;
18 import java.io.InputStream;
19 import java.math.BigDecimal;
20 import java.net.URI;
21 import java.net.URISyntaxException;
22 import java.util.Properties;
23 import java.util.concurrent.Future;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.eclipse.jetty.client.HttpClient;
30 import org.eclipse.jetty.http.HttpHeader;
31 import org.eclipse.jetty.util.ssl.SslContextFactory;
32 import org.eclipse.jetty.websocket.api.Session;
33 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
34 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
35 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
36 import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
37 import org.eclipse.jetty.websocket.api.annotations.WebSocket;
38 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
39 import org.eclipse.jetty.websocket.client.WebSocketClient;
40 import org.openhab.binding.tibber.internal.config.TibberConfiguration;
41 import org.openhab.core.io.net.http.HttpUtil;
42 import org.openhab.core.library.types.DateTimeType;
43 import org.openhab.core.library.types.DecimalType;
44 import org.openhab.core.library.types.QuantityType;
45 import org.openhab.core.library.types.StringType;
46 import org.openhab.core.library.unit.Units;
47 import org.openhab.core.thing.ChannelUID;
48 import org.openhab.core.thing.Thing;
49 import org.openhab.core.thing.ThingStatus;
50 import org.openhab.core.thing.ThingStatusDetail;
51 import org.openhab.core.thing.ThingStatusInfo;
52 import org.openhab.core.thing.binding.BaseThingHandler;
53 import org.openhab.core.types.Command;
54 import org.openhab.core.types.RefreshType;
55 import org.osgi.framework.FrameworkUtil;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59 import com.google.gson.JsonArray;
60 import com.google.gson.JsonObject;
61 import com.google.gson.JsonParser;
62 import com.google.gson.JsonSyntaxException;
63
64 /**
65  * The {@link TibberHandler} is responsible for handling queries to/from Tibber API.
66  *
67  * @author Stian Kjoglum - Initial contribution
68  */
69 @NonNullByDefault
70 public class TibberHandler extends BaseThingHandler {
71     private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20);
72     private final Logger logger = LoggerFactory.getLogger(TibberHandler.class);
73     private final Properties httpHeader = new Properties();
74     private TibberConfiguration tibberConfig = new TibberConfiguration();
75     private @Nullable SslContextFactory sslContextFactory;
76     private @Nullable TibberWebSocketListener socket;
77     private @Nullable Session session;
78     private @Nullable WebSocketClient client;
79     private @Nullable ScheduledFuture<?> pollingJob;
80     private @Nullable Future<?> sessionFuture;
81     private String rtEnabled = "false";
82     private @Nullable String subscriptionURL;
83     private @Nullable String versionString;
84
85     public TibberHandler(Thing thing) {
86         super(thing);
87     }
88
89     @Override
90     public void initialize() {
91         updateStatus(ThingStatus.UNKNOWN);
92         tibberConfig = getConfigAs(TibberConfiguration.class);
93
94         versionString = FrameworkUtil.getBundle(this.getClass()).getVersion().toString();
95         logger.debug("Binding version: {}", versionString);
96
97         getTibberParameters();
98         startRefresh(tibberConfig.getRefresh());
99     }
100
101     @Override
102     public void handleCommand(ChannelUID channelUID, Command command) {
103         if (command instanceof RefreshType) {
104             startRefresh(tibberConfig.getRefresh());
105         } else {
106             logger.debug("Tibber API is read-only and does not handle commands");
107         }
108     }
109
110     public void getTibberParameters() {
111         String response = "";
112         try {
113             httpHeader.put("cache-control", "no-cache");
114             httpHeader.put("content-type", JSON_CONTENT_TYPE);
115             httpHeader.put(HttpHeader.USER_AGENT.asString(),
116                     "openHAB/Tibber " + versionString + " Tibber driver " + TIBBER_DRIVER);
117             httpHeader.put(HttpHeader.AUTHORIZATION.asString(), "Bearer " + tibberConfig.getToken());
118
119             TibberPriceConsumptionHandler tibberQuery = new TibberPriceConsumptionHandler();
120             InputStream connectionStream = tibberQuery.connectionInputStream(tibberConfig.getHomeid());
121             response = HttpUtil.executeUrl("POST", BASE_URL, httpHeader, connectionStream, null, REQUEST_TIMEOUT);
122
123             if (!response.contains("error") && !response.contains("<html>")) {
124                 updateStatus(ThingStatus.ONLINE);
125                 getURLInput(BASE_URL);
126
127                 InputStream inputStream = tibberQuery.getRealtimeInputStream(tibberConfig.getHomeid());
128                 String jsonResponse = HttpUtil.executeUrl("POST", BASE_URL, httpHeader, inputStream, null,
129                         REQUEST_TIMEOUT);
130
131                 JsonObject object = (JsonObject) JsonParser.parseString(jsonResponse);
132                 rtEnabled = object.getAsJsonObject("data").getAsJsonObject("viewer").getAsJsonObject("home")
133                         .getAsJsonObject("features").get("realTimeConsumptionEnabled").toString();
134
135                 if ("true".equals(rtEnabled)) {
136                     logger.debug("Pulse associated with HomeId: Live stream will be started");
137
138                     InputStream wsURL = tibberQuery.getWebsocketUrl();
139                     String wsResponse = HttpUtil.executeUrl("POST", BASE_URL, httpHeader, wsURL, null, REQUEST_TIMEOUT);
140
141                     JsonObject wsobject = (JsonObject) JsonParser.parseString(wsResponse);
142                     subscriptionURL = wsobject.getAsJsonObject("data").getAsJsonObject("viewer")
143                             .get("websocketSubscriptionUrl").toString().replaceAll("^\"|\"$", "");
144                     logger.debug("Subscribing to: {}", subscriptionURL);
145
146                     open();
147                 } else {
148                     logger.debug("No Pulse associated with HomeId: No live stream will be started");
149                 }
150             } else {
151                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
152                         "Problems connecting/communicating with server: " + response);
153             }
154         } catch (IOException | JsonSyntaxException e) {
155             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
156         }
157     }
158
159     public void getURLInput(String url) throws IOException {
160         String jsonResponse = "";
161         TibberPriceConsumptionHandler tibberQuery = new TibberPriceConsumptionHandler();
162
163         InputStream inputStream = tibberQuery.getInputStream(tibberConfig.getHomeid());
164         jsonResponse = HttpUtil.executeUrl("POST", url, httpHeader, inputStream, null, REQUEST_TIMEOUT);
165         logger.debug("API response: {}", jsonResponse);
166
167         if (!jsonResponse.contains("error") && !jsonResponse.contains("<html>")) {
168             if (getThing().getStatus() == ThingStatus.OFFLINE || getThing().getStatus() == ThingStatus.INITIALIZING) {
169                 updateStatus(ThingStatus.ONLINE);
170             }
171
172             JsonObject rootJsonObject = (JsonObject) JsonParser.parseString(jsonResponse);
173
174             if (jsonResponse.contains("total")) {
175                 try {
176                     JsonObject current = rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
177                             .getAsJsonObject("home").getAsJsonObject("currentSubscription").getAsJsonObject("priceInfo")
178                             .getAsJsonObject("current");
179
180                     updateState(CURRENT_TOTAL, new DecimalType(current.get("total").toString()));
181                     String timestamp = current.get("startsAt").toString().substring(1, 20);
182                     updateState(CURRENT_STARTSAT, new DateTimeType(timestamp));
183                     updateState(CURRENT_LEVEL,
184                             new StringType(current.get("level").toString().replaceAll("^\"|\"$", "")));
185
186                     JsonArray tomorrow = rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
187                             .getAsJsonObject("home").getAsJsonObject("currentSubscription").getAsJsonObject("priceInfo")
188                             .getAsJsonArray("tomorrow");
189                     updateState(TOMORROW_PRICES, new StringType(tomorrow.toString()));
190                     JsonArray today = rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
191                             .getAsJsonObject("home").getAsJsonObject("currentSubscription").getAsJsonObject("priceInfo")
192                             .getAsJsonArray("today");
193                     updateState(TODAY_PRICES, new StringType(today.toString()));
194                 } catch (JsonSyntaxException e) {
195                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
196                             "Error communicating with Tibber API: " + e.getMessage());
197                 }
198             }
199             if (jsonResponse.contains("daily") && !jsonResponse.contains("\"daily\":{\"nodes\":[]")
200                     && !jsonResponse.contains("\"daily\":null")) {
201                 try {
202                     JsonObject myObject = (JsonObject) rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
203                             .getAsJsonObject("home").getAsJsonObject("daily").getAsJsonArray("nodes").get(0);
204
205                     String timestampDailyFrom = myObject.get("from").toString().substring(1, 20);
206                     updateState(DAILY_FROM, new DateTimeType(timestampDailyFrom));
207
208                     String timestampDailyTo = myObject.get("to").toString().substring(1, 20);
209                     updateState(DAILY_TO, new DateTimeType(timestampDailyTo));
210
211                     updateChannel(DAILY_COST, myObject.get("cost").toString());
212                     updateChannel(DAILY_CONSUMPTION, myObject.get("consumption").toString());
213                 } catch (JsonSyntaxException e) {
214                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
215                             "Error communicating with Tibber API: " + e.getMessage());
216                 }
217             }
218             if (jsonResponse.contains("hourly") && !jsonResponse.contains("\"hourly\":{\"nodes\":[]")
219                     && !jsonResponse.contains("\"hourly\":null")) {
220                 try {
221                     JsonObject myObject = (JsonObject) rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
222                             .getAsJsonObject("home").getAsJsonObject("hourly").getAsJsonArray("nodes").get(0);
223
224                     String timestampHourlyFrom = myObject.get("from").toString().substring(1, 20);
225                     updateState(HOURLY_FROM, new DateTimeType(timestampHourlyFrom));
226
227                     String timestampHourlyTo = myObject.get("to").toString().substring(1, 20);
228                     updateState(HOURLY_TO, new DateTimeType(timestampHourlyTo));
229
230                     updateChannel(HOURLY_COST, myObject.get("cost").toString());
231                     updateChannel(HOURLY_CONSUMPTION, myObject.get("consumption").toString());
232                 } catch (JsonSyntaxException e) {
233                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
234                             "Error communicating with Tibber API: " + e.getMessage());
235                 }
236             }
237         } else if (jsonResponse.contains("error")) {
238             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
239                     "Error in response from Tibber API: " + jsonResponse);
240             try {
241                 Thread.sleep(300 * 1000);
242                 return;
243             } catch (InterruptedException e) {
244                 logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
245             }
246         } else {
247             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
248                     "Unexpected response from Tibber: " + jsonResponse);
249             try {
250                 Thread.sleep(300 * 1000);
251                 return;
252             } catch (InterruptedException e) {
253                 logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
254             }
255         }
256     }
257
258     public void startRefresh(int refresh) {
259         if (pollingJob == null) {
260             pollingJob = scheduler.scheduleWithFixedDelay(() -> {
261                 try {
262                     updateRequest();
263                 } catch (IOException e) {
264                     logger.warn("IO Exception: {}", e.getMessage());
265                 }
266             }, 1, refresh, TimeUnit.MINUTES);
267         }
268     }
269
270     public void updateRequest() throws IOException {
271         getURLInput(BASE_URL);
272         if ("true".equals(rtEnabled) && !isConnected()) {
273             logger.debug("Attempting to reopen Websocket connection");
274             open();
275         }
276     }
277
278     public void updateChannel(String channelID, String channelValue) {
279         if (!channelValue.contains("null")) {
280             if (channelID.contains("consumption") || channelID.contains("Consumption")
281                     || channelID.contains("accumulatedProduction")) {
282                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.KILOWATT_HOUR));
283             } else if (channelID.contains("power") || channelID.contains("Power")) {
284                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.WATT));
285             } else if (channelID.contains("voltage")) {
286                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.VOLT));
287             } else if (channelID.contains("current")) {
288                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.AMPERE));
289             } else {
290                 updateState(channelID, new DecimalType(channelValue));
291             }
292         }
293     }
294
295     public void thingStatusChanged(ThingStatusInfo thingStatusInfo) {
296         logger.debug("Thing Status updated to {} for device: {}", thingStatusInfo.getStatus(), getThing().getUID());
297         if (thingStatusInfo.getStatus() != ThingStatus.ONLINE) {
298             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
299                     "Unable to communicate with Tibber API");
300         }
301     }
302
303     @Override
304     public void dispose() {
305         ScheduledFuture<?> pollingJob = this.pollingJob;
306         if (pollingJob != null) {
307             pollingJob.cancel(true);
308             this.pollingJob = null;
309         }
310         if (isConnected()) {
311             close();
312             WebSocketClient client = this.client;
313             if (client != null) {
314                 try {
315                     logger.debug("DISPOSE - Stopping and Terminating Websocket connection");
316                     client.stop();
317                 } catch (Exception e) {
318                     logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
319                 }
320                 client.destroy();
321                 this.client = null;
322             }
323         }
324         super.dispose();
325     }
326
327     public void open() {
328         WebSocketClient client = this.client;
329         if (client == null || !client.isRunning() || !isConnected()) {
330             if (client != null) {
331                 try {
332                     client.stop();
333                 } catch (Exception e) {
334                     logger.warn("OPEN FRAME - Failed to stop websocket client: {}", e.getMessage());
335                 }
336                 client.destroy();
337             }
338             sslContextFactory = new SslContextFactory.Client(true);
339             sslContextFactory.setTrustAll(true);
340             sslContextFactory.setEndpointIdentificationAlgorithm(null);
341
342             client = new WebSocketClient(new HttpClient(sslContextFactory));
343             client.setMaxIdleTimeout(30 * 1000);
344             this.client = client;
345
346             TibberWebSocketListener socket = this.socket;
347             if (socket == null) {
348                 logger.debug("New socket being created");
349                 socket = new TibberWebSocketListener();
350                 this.socket = socket;
351             }
352
353             ClientUpgradeRequest newRequest = new ClientUpgradeRequest();
354             newRequest.setHeader(HttpHeader.USER_AGENT.asString(),
355                     "openHAB/Tibber " + versionString + " Tibber driver " + TIBBER_DRIVER);
356             newRequest.setHeader(HttpHeader.AUTHORIZATION.asString(), "Bearer " + tibberConfig.getToken());
357             newRequest.setSubProtocols("graphql-transport-ws");
358
359             try {
360                 logger.debug("Starting Websocket connection");
361                 client.start();
362             } catch (Exception e) {
363                 logger.warn("Websocket Start Exception: {}", e.getMessage());
364             }
365             try {
366                 logger.debug("Connecting Websocket connection");
367                 sessionFuture = client.connect(socket, new URI(subscriptionURL), newRequest);
368                 try {
369                     Thread.sleep(10 * 1000);
370                 } catch (InterruptedException e) {
371                 }
372                 if (!isConnected()) {
373                     logger.warn("Unable to establish websocket session - Reattempting connection on next refresh");
374                 } else {
375                     logger.debug("Websocket session established");
376                 }
377             } catch (IOException e) {
378                 logger.warn("Websocket Connect Exception: {}", e.getMessage());
379             } catch (URISyntaxException e) {
380                 logger.warn("Websocket URI Exception: {}", e.getMessage());
381             }
382         } else {
383             logger.warn("Open: Websocket client already running");
384         }
385     }
386
387     public void close() {
388         Session session = this.session;
389         if (session != null) {
390             String disconnect = "{\"type\":\"connection_terminate\",\"payload\":null}";
391             try {
392                 TibberWebSocketListener socket = this.socket;
393                 if (socket != null) {
394                     logger.debug("Sending websocket disconnect message");
395                     socket.sendMessage(disconnect);
396                 } else {
397                     logger.warn("Socket unable to send disconnect message: Socket is null");
398                 }
399             } catch (IOException e) {
400                 logger.warn("Websocket Close Exception: {}", e.getMessage());
401             }
402             try {
403                 session.close();
404             } catch (Exception e) {
405                 logger.warn("Unable to disconnect session");
406             }
407             this.session = null;
408             this.socket = null;
409         }
410         Future<?> sessionFuture = this.sessionFuture;
411         if (sessionFuture != null && !sessionFuture.isDone()) {
412             sessionFuture.cancel(true);
413         }
414         WebSocketClient client = this.client;
415         if (client != null) {
416             try {
417                 client.stop();
418             } catch (Exception e) {
419                 logger.warn("CLOSE FRAME - Failed to stop websocket client: {}", e.getMessage());
420             }
421             client.destroy();
422         }
423     }
424
425     public boolean isConnected() {
426         Session session = this.session;
427         return session != null && session.isOpen();
428     }
429
430     @WebSocket
431     public class TibberWebSocketListener {
432
433         @OnWebSocketConnect
434         public void onConnect(Session wssession) {
435             TibberHandler.this.session = wssession;
436             TibberWebSocketListener socket = TibberHandler.this.socket;
437             String connection = "{\"type\":\"connection_init\", \"payload\":{\"token\":\"" + tibberConfig.getToken()
438                     + "\"}}";
439             try {
440                 if (socket != null) {
441                     logger.debug("Sending websocket connect message");
442                     socket.sendMessage(connection);
443                 } else {
444                     logger.debug("Socket unable to send connect message: Socket is null");
445                 }
446             } catch (IOException e) {
447                 logger.warn("Send Message Exception: {}", e.getMessage());
448             }
449         }
450
451         @OnWebSocketClose
452         public void onClose(int statusCode, String reason) {
453             logger.debug("Closing a WebSocket due to {}", reason);
454             WebSocketClient client = TibberHandler.this.client;
455             if (client != null && client.isRunning()) {
456                 try {
457                     logger.debug("ONCLOSE - Stopping and Terminating Websocket connection");
458                     client.stop();
459                 } catch (Exception e) {
460                     logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
461                 }
462             }
463         }
464
465         @OnWebSocketError
466         public void onWebSocketError(Throwable e) {
467             String message = e.getMessage();
468             logger.debug("Error during websocket communication: {}", message);
469             close();
470         }
471
472         @OnWebSocketMessage
473         public void onMessage(String message) {
474             if (message.contains("connection_ack")) {
475                 logger.debug("Connected to Server");
476                 startSubscription();
477             } else if (message.contains("error") || message.contains("terminate")) {
478                 logger.debug("Error/terminate received from server: {}", message);
479                 close();
480             } else if (message.contains("liveMeasurement")) {
481                 JsonObject object = (JsonObject) JsonParser.parseString(message);
482                 JsonObject myObject = object.getAsJsonObject("payload").getAsJsonObject("data")
483                         .getAsJsonObject("liveMeasurement");
484                 if (myObject.has("timestamp")) {
485                     String liveTimestamp = myObject.get("timestamp").toString().substring(1, 20);
486                     updateState(LIVE_TIMESTAMP, new DateTimeType(liveTimestamp));
487                 }
488                 if (myObject.has("power")) {
489                     updateChannel(LIVE_POWER, myObject.get("power").toString());
490                 }
491                 if (myObject.has("lastMeterConsumption")) {
492                     updateChannel(LIVE_LASTMETERCONSUMPTION, myObject.get("lastMeterConsumption").toString());
493                 }
494                 if (myObject.has("accumulatedConsumption")) {
495                     updateChannel(LIVE_ACCUMULATEDCONSUMPTION, myObject.get("accumulatedConsumption").toString());
496                 }
497                 if (myObject.has("accumulatedCost")) {
498                     updateChannel(LIVE_ACCUMULATEDCOST, myObject.get("accumulatedCost").toString());
499                 }
500                 if (myObject.has("accumulatedReward")) {
501                     updateChannel(LIVE_ACCUMULATEREWARD, myObject.get("accumulatedReward").toString());
502                 }
503                 if (myObject.has("currency")) {
504                     updateState(LIVE_CURRENCY, new StringType(myObject.get("currency").toString()));
505                 }
506                 if (myObject.has("minPower")) {
507                     updateChannel(LIVE_MINPOWER, myObject.get("minPower").toString());
508                 }
509                 if (myObject.has("averagePower")) {
510                     updateChannel(LIVE_AVERAGEPOWER, myObject.get("averagePower").toString());
511                 }
512                 if (myObject.has("maxPower")) {
513                     updateChannel(LIVE_MAXPOWER, myObject.get("maxPower").toString());
514                 }
515                 if (myObject.has("voltagePhase1")) {
516                     updateChannel(LIVE_VOLTAGE1, myObject.get("voltagePhase1").toString());
517                 }
518                 if (myObject.has("voltagePhase2")) {
519                     updateChannel(LIVE_VOLTAGE2, myObject.get("voltagePhase2").toString());
520                 }
521                 if (myObject.has("voltagePhase3")) {
522                     updateChannel(LIVE_VOLTAGE3, myObject.get("voltagePhase3").toString());
523                 }
524                 if (myObject.has("currentL1")) {
525                     updateChannel(LIVE_CURRENT1, myObject.get("currentL1").toString());
526                 }
527                 if (myObject.has("currentL2")) {
528                     updateChannel(LIVE_CURRENT2, myObject.get("currentL2").toString());
529                 }
530                 if (myObject.has("currentL3")) {
531                     updateChannel(LIVE_CURRENT3, myObject.get("currentL3").toString());
532                 }
533                 if (myObject.has("powerProduction")) {
534                     updateChannel(LIVE_POWERPRODUCTION, myObject.get("powerProduction").toString());
535                 }
536                 if (myObject.has("accumulatedProduction")) {
537                     updateChannel(LIVE_ACCUMULATEDPRODUCTION, myObject.get("accumulatedProduction").toString());
538                 }
539                 if (myObject.has("minPowerProduction")) {
540                     updateChannel(LIVE_MINPOWERPRODUCTION, myObject.get("minPowerProduction").toString());
541                 }
542                 if (myObject.has("maxPowerProduction")) {
543                     updateChannel(LIVE_MAXPOWERPRODUCTION, myObject.get("maxPowerProduction").toString());
544                 }
545             } else {
546                 logger.debug("Unknown live response from Tibber");
547             }
548         }
549
550         private void sendMessage(String message) throws IOException {
551             logger.debug("Send message: {}", message);
552             Session session = TibberHandler.this.session;
553             if (session != null) {
554                 session.getRemote().sendString(message);
555             }
556         }
557
558         public void startSubscription() {
559             String query = "{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":null,\"query\":\"subscription {\\n liveMeasurement(homeId:\\\""
560                     + tibberConfig.getHomeid()
561                     + "\\\") {\\n timestamp\\n power\\n lastMeterConsumption\\n accumulatedConsumption\\n accumulatedCost\\n accumulatedReward\\n currency\\n minPower\\n averagePower\\n maxPower\\n"
562                     + "voltagePhase1\\n voltagePhase2\\n voltagePhase3\\n currentL1\\n currentL2\\n currentL3\\n powerProduction\\n accumulatedProduction\\n minPowerProduction\\n maxPowerProduction\\n }\\n }\\n\"}}";
563             try {
564                 TibberWebSocketListener socket = TibberHandler.this.socket;
565                 if (socket != null) {
566                     socket.sendMessage(query);
567                 } else {
568                     logger.debug("Socket unable to send subscription message: Socket is null");
569                 }
570             } catch (IOException e) {
571                 logger.warn("Send Message Exception: {}", e.getMessage());
572             }
573         }
574     }
575 }