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