]> git.basschouten.com Git - openhab-addons.git/blob
27ca48e64d6696861f2c722bb1dad9298f539ead
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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                 } catch (JsonSyntaxException e) {
191                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
192                             "Error communicating with Tibber API: " + e.getMessage());
193                 }
194             }
195             if (jsonResponse.contains("daily") && !jsonResponse.contains("\"daily\":{\"nodes\":[]")
196                     && !jsonResponse.contains("\"daily\":null")) {
197                 try {
198                     JsonObject myObject = (JsonObject) rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
199                             .getAsJsonObject("home").getAsJsonObject("daily").getAsJsonArray("nodes").get(0);
200
201                     String timestampDailyFrom = myObject.get("from").toString().substring(1, 20);
202                     updateState(DAILY_FROM, new DateTimeType(timestampDailyFrom));
203
204                     String timestampDailyTo = myObject.get("to").toString().substring(1, 20);
205                     updateState(DAILY_TO, new DateTimeType(timestampDailyTo));
206
207                     updateChannel(DAILY_COST, myObject.get("cost").toString());
208                     updateChannel(DAILY_CONSUMPTION, myObject.get("consumption").toString());
209
210                 } catch (JsonSyntaxException e) {
211                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
212                             "Error communicating with Tibber API: " + e.getMessage());
213                 }
214             }
215             if (jsonResponse.contains("hourly") && !jsonResponse.contains("\"hourly\":{\"nodes\":[]")
216                     && !jsonResponse.contains("\"hourly\":null")) {
217                 try {
218                     JsonObject myObject = (JsonObject) rootJsonObject.getAsJsonObject("data").getAsJsonObject("viewer")
219                             .getAsJsonObject("home").getAsJsonObject("hourly").getAsJsonArray("nodes").get(0);
220
221                     String timestampHourlyFrom = myObject.get("from").toString().substring(1, 20);
222                     updateState(HOURLY_FROM, new DateTimeType(timestampHourlyFrom));
223
224                     String timestampHourlyTo = myObject.get("to").toString().substring(1, 20);
225                     updateState(HOURLY_TO, new DateTimeType(timestampHourlyTo));
226
227                     updateChannel(HOURLY_COST, myObject.get("cost").toString());
228                     updateChannel(HOURLY_CONSUMPTION, myObject.get("consumption").toString());
229
230                 } catch (JsonSyntaxException e) {
231                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
232                             "Error communicating with Tibber API: " + e.getMessage());
233                 }
234             }
235         } else if (jsonResponse.contains("error")) {
236             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
237                     "Error in response from Tibber API: " + jsonResponse);
238             try {
239                 Thread.sleep(300 * 1000);
240                 return;
241             } catch (InterruptedException e) {
242                 logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
243             }
244         } else {
245             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
246                     "Unexpected response from Tibber: " + jsonResponse);
247             try {
248                 Thread.sleep(300 * 1000);
249                 return;
250             } catch (InterruptedException e) {
251                 logger.debug("Tibber OFFLINE, attempting thread sleep: {}", e.getMessage());
252             }
253         }
254     }
255
256     public void startRefresh(int refresh) {
257         if (pollingJob == null) {
258             pollingJob = scheduler.scheduleWithFixedDelay(() -> {
259                 try {
260                     updateRequest();
261                 } catch (IOException e) {
262                     logger.warn("IO Exception: {}", e.getMessage());
263                 }
264             }, 1, refresh, TimeUnit.MINUTES);
265         }
266     }
267
268     public void updateRequest() throws IOException {
269         getURLInput(BASE_URL);
270         if ("true".equals(rtEnabled) && !isConnected()) {
271             logger.debug("Attempting to reopen Websocket connection");
272             open();
273         }
274     }
275
276     public void updateChannel(String channelID, String channelValue) {
277         if (!channelValue.contains("null")) {
278             if (channelID.contains("consumption") || channelID.contains("Consumption")
279                     || channelID.contains("accumulatedProduction")) {
280                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.KILOWATT_HOUR));
281             } else if (channelID.contains("power") || channelID.contains("Power")) {
282                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.WATT));
283             } else if (channelID.contains("voltage")) {
284                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.VOLT));
285             } else if (channelID.contains("current")) {
286                 updateState(channelID, new QuantityType<>(new BigDecimal(channelValue), Units.AMPERE));
287             } else {
288                 updateState(channelID, new DecimalType(channelValue));
289             }
290         }
291     }
292
293     public void thingStatusChanged(ThingStatusInfo thingStatusInfo) {
294         logger.debug("Thing Status updated to {} for device: {}", thingStatusInfo.getStatus(), getThing().getUID());
295         if (thingStatusInfo.getStatus() != ThingStatus.ONLINE) {
296             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
297                     "Unable to communicate with Tibber API");
298         }
299     }
300
301     @Override
302     public void dispose() {
303         ScheduledFuture<?> pollingJob = this.pollingJob;
304         if (pollingJob != null) {
305             pollingJob.cancel(true);
306             this.pollingJob = null;
307         }
308         if (isConnected()) {
309             close();
310             WebSocketClient client = this.client;
311             if (client != null) {
312                 try {
313                     logger.debug("DISPOSE - Stopping and Terminating Websocket connection");
314                     client.stop();
315                 } catch (Exception e) {
316                     logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
317                 }
318                 client.destroy();
319                 this.client = null;
320             }
321         }
322         super.dispose();
323     }
324
325     public void open() {
326         WebSocketClient client = this.client;
327         if (client == null || !client.isRunning() || !isConnected()) {
328             if (client != null) {
329                 try {
330                     client.stop();
331                 } catch (Exception e) {
332                     logger.warn("OPEN FRAME - Failed to stop websocket client: {}", e.getMessage());
333                 }
334                 client.destroy();
335             }
336             sslContextFactory = new SslContextFactory.Client(true);
337             sslContextFactory.setTrustAll(true);
338             sslContextFactory.setEndpointIdentificationAlgorithm(null);
339
340             client = new WebSocketClient(new HttpClient(sslContextFactory));
341             client.setMaxIdleTimeout(30 * 1000);
342             this.client = client;
343
344             TibberWebSocketListener socket = this.socket;
345             if (socket == null) {
346                 logger.debug("New socket being created");
347                 socket = new TibberWebSocketListener();
348                 this.socket = socket;
349             }
350
351             ClientUpgradeRequest newRequest = new ClientUpgradeRequest();
352             newRequest.setHeader(HttpHeader.USER_AGENT.asString(),
353                     "openHAB/Tibber " + versionString + " Tibber driver " + TIBBER_DRIVER);
354             newRequest.setHeader(HttpHeader.AUTHORIZATION.asString(), "Bearer " + tibberConfig.getToken());
355             newRequest.setSubProtocols("graphql-transport-ws");
356
357             try {
358                 logger.debug("Starting Websocket connection");
359                 client.start();
360             } catch (Exception e) {
361                 logger.warn("Websocket Start Exception: {}", e.getMessage());
362             }
363             try {
364                 logger.debug("Connecting Websocket connection");
365                 sessionFuture = client.connect(socket, new URI(subscriptionURL), newRequest);
366                 try {
367                     Thread.sleep(10 * 1000);
368                 } catch (InterruptedException e) {
369                 }
370                 if (!isConnected()) {
371                     logger.warn("Unable to establish websocket session - Reattempting connection on next refresh");
372                 } else {
373                     logger.debug("Websocket session established");
374                 }
375             } catch (IOException e) {
376                 logger.warn("Websocket Connect Exception: {}", e.getMessage());
377             } catch (URISyntaxException e) {
378                 logger.warn("Websocket URI Exception: {}", e.getMessage());
379             }
380         } else {
381             logger.warn("Open: Websocket client already running");
382         }
383     }
384
385     public void close() {
386         Session session = this.session;
387         if (session != null) {
388             String disconnect = "{\"type\":\"connection_terminate\",\"payload\":null}";
389             try {
390                 TibberWebSocketListener socket = this.socket;
391                 if (socket != null) {
392                     logger.debug("Sending websocket disconnect message");
393                     socket.sendMessage(disconnect);
394                 } else {
395                     logger.warn("Socket unable to send disconnect message: Socket is null");
396                 }
397             } catch (IOException e) {
398                 logger.warn("Websocket Close Exception: {}", e.getMessage());
399             }
400             try {
401                 session.close();
402             } catch (Exception e) {
403                 logger.warn("Unable to disconnect session");
404             }
405             this.session = null;
406             this.socket = null;
407         }
408         Future<?> sessionFuture = this.sessionFuture;
409         if (sessionFuture != null && !sessionFuture.isDone()) {
410             sessionFuture.cancel(true);
411         }
412         WebSocketClient client = this.client;
413         if (client != null) {
414             try {
415                 client.stop();
416             } catch (Exception e) {
417                 logger.warn("CLOSE FRAME - Failed to stop websocket client: {}", e.getMessage());
418             }
419             client.destroy();
420         }
421     }
422
423     public boolean isConnected() {
424         Session session = this.session;
425         return session != null && session.isOpen();
426     }
427
428     @WebSocket
429     @NonNullByDefault
430     public class TibberWebSocketListener {
431
432         @OnWebSocketConnect
433         public void onConnect(Session wssession) {
434             TibberHandler.this.session = wssession;
435             TibberWebSocketListener socket = TibberHandler.this.socket;
436             String connection = "{\"type\":\"connection_init\", \"payload\":{\"token\":\"" + tibberConfig.getToken()
437                     + "\"}}";
438             try {
439                 if (socket != null) {
440                     logger.debug("Sending websocket connect message");
441                     socket.sendMessage(connection);
442                 } else {
443                     logger.debug("Socket unable to send connect message: Socket is null");
444                 }
445             } catch (IOException e) {
446                 logger.warn("Send Message Exception: {}", e.getMessage());
447             }
448         }
449
450         @OnWebSocketClose
451         public void onClose(int statusCode, String reason) {
452             logger.debug("Closing a WebSocket due to {}", reason);
453             WebSocketClient client = TibberHandler.this.client;
454             if (client != null && client.isRunning()) {
455                 try {
456                     logger.debug("ONCLOSE - Stopping and Terminating Websocket connection");
457                     client.stop();
458                 } catch (Exception e) {
459                     logger.warn("Websocket Client Stop Exception: {}", e.getMessage());
460                 }
461             }
462         }
463
464         @OnWebSocketError
465         public void onWebSocketError(Throwable e) {
466             String message = e.getMessage();
467             logger.debug("Error during websocket communication: {}", message);
468             close();
469         }
470
471         @OnWebSocketMessage
472         public void onMessage(String message) {
473             if (message.contains("connection_ack")) {
474                 logger.debug("Connected to Server");
475                 startSubscription();
476             } else if (message.contains("error") || message.contains("terminate")) {
477                 logger.debug("Error/terminate received from server: {}", message);
478                 close();
479             } else if (message.contains("liveMeasurement")) {
480                 JsonObject object = (JsonObject) JsonParser.parseString(message);
481                 JsonObject myObject = object.getAsJsonObject("payload").getAsJsonObject("data")
482                         .getAsJsonObject("liveMeasurement");
483                 if (myObject.has("timestamp")) {
484                     String liveTimestamp = myObject.get("timestamp").toString().substring(1, 20);
485                     updateState(LIVE_TIMESTAMP, new DateTimeType(liveTimestamp));
486                 }
487                 if (myObject.has("power")) {
488                     updateChannel(LIVE_POWER, myObject.get("power").toString());
489                 }
490                 if (myObject.has("lastMeterConsumption")) {
491                     updateChannel(LIVE_LASTMETERCONSUMPTION, myObject.get("lastMeterConsumption").toString());
492                 }
493                 if (myObject.has("accumulatedConsumption")) {
494                     updateChannel(LIVE_ACCUMULATEDCONSUMPTION, myObject.get("accumulatedConsumption").toString());
495                 }
496                 if (myObject.has("accumulatedCost")) {
497                     updateChannel(LIVE_ACCUMULATEDCOST, myObject.get("accumulatedCost").toString());
498                 }
499                 if (myObject.has("currency")) {
500                     updateState(LIVE_CURRENCY, new StringType(myObject.get("currency").toString()));
501                 }
502                 if (myObject.has("minPower")) {
503                     updateChannel(LIVE_MINPOWER, myObject.get("minPower").toString());
504                 }
505                 if (myObject.has("averagePower")) {
506                     updateChannel(LIVE_AVERAGEPOWER, myObject.get("averagePower").toString());
507                 }
508                 if (myObject.has("maxPower")) {
509                     updateChannel(LIVE_MAXPOWER, myObject.get("maxPower").toString());
510                 }
511                 if (myObject.has("voltagePhase1")) {
512                     updateChannel(LIVE_VOLTAGE1, myObject.get("voltagePhase1").toString());
513                 }
514                 if (myObject.has("voltagePhase2")) {
515                     updateChannel(LIVE_VOLTAGE2, myObject.get("voltagePhase2").toString());
516                 }
517                 if (myObject.has("voltagePhase3")) {
518                     updateChannel(LIVE_VOLTAGE3, myObject.get("voltagePhase3").toString());
519                 }
520                 if (myObject.has("currentL1")) {
521                     updateChannel(LIVE_CURRENT1, myObject.get("currentL1").toString());
522                 }
523                 if (myObject.has("currentL2")) {
524                     updateChannel(LIVE_CURRENT2, myObject.get("currentL2").toString());
525                 }
526                 if (myObject.has("currentL3")) {
527                     updateChannel(LIVE_CURRENT3, myObject.get("currentL3").toString());
528                 }
529                 if (myObject.has("powerProduction")) {
530                     updateChannel(LIVE_POWERPRODUCTION, myObject.get("powerProduction").toString());
531                 }
532                 if (myObject.has("accumulatedProduction")) {
533                     updateChannel(LIVE_ACCUMULATEDPRODUCTION, myObject.get("accumulatedProduction").toString());
534                 }
535                 if (myObject.has("minPowerProduction")) {
536                     updateChannel(LIVE_MINPOWERPRODUCTION, myObject.get("minPowerProduction").toString());
537                 }
538                 if (myObject.has("maxPowerProduction")) {
539                     updateChannel(LIVE_MAXPOWERPRODUCTION, myObject.get("maxPowerProduction").toString());
540                 }
541             } else {
542                 logger.debug("Unknown live response from Tibber");
543             }
544         }
545
546         private void sendMessage(String message) throws IOException {
547             logger.debug("Send message: {}", message);
548             Session session = TibberHandler.this.session;
549             if (session != null) {
550                 session.getRemote().sendString(message);
551             }
552         }
553
554         public void startSubscription() {
555             String query = "{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":null,\"query\":\"subscription {\\n liveMeasurement(homeId:\\\""
556                     + tibberConfig.getHomeid()
557                     + "\\\") {\\n timestamp\\n power\\n lastMeterConsumption\\n accumulatedConsumption\\n accumulatedCost\\n currency\\n minPower\\n averagePower\\n maxPower\\n"
558                     + "voltagePhase1\\n voltagePhase2\\n voltagePhase3\\n currentL1\\n currentL2\\n currentL3\\n powerProduction\\n accumulatedProduction\\n minPowerProduction\\n maxPowerProduction\\n }\\n }\\n\"}}";
559             try {
560                 TibberWebSocketListener socket = TibberHandler.this.socket;
561                 if (socket != null) {
562                     socket.sendMessage(query);
563                 } else {
564                     logger.debug("Socket unable to send subscription message: Socket is null");
565                 }
566             } catch (IOException e) {
567                 logger.warn("Send Message Exception: {}", e.getMessage());
568             }
569         }
570     }
571 }