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.freeboxos.internal.api.rest;
15 import java.io.IOException;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Locale;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.websocket.api.Session;
25 import org.eclipse.jetty.websocket.api.StatusCode;
26 import org.eclipse.jetty.websocket.api.WebSocketListener;
27 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
28 import org.eclipse.jetty.websocket.client.WebSocketClient;
29 import org.openhab.binding.freeboxos.internal.api.ApiHandler;
30 import org.openhab.binding.freeboxos.internal.api.FreeboxException;
31 import org.openhab.binding.freeboxos.internal.api.rest.LanBrowserManager.LanHost;
32 import org.openhab.binding.freeboxos.internal.api.rest.VmManager.VirtualMachine;
33 import org.openhab.binding.freeboxos.internal.handler.HostHandler;
34 import org.openhab.binding.freeboxos.internal.handler.VmHandler;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 import com.google.gson.JsonElement;
40 import inet.ipaddr.mac.MACAddress;
43 * The {@link WebSocketManager} is the Java class register to the websocket server and handle notifications
45 * @author Gaƫl L'hopital - Initial contribution
48 public class WebSocketManager extends RestManager implements WebSocketListener {
49 private static final String HOST_UNREACHABLE = "lan_host_l3addr_unreachable";
50 private static final String HOST_REACHABLE = "lan_host_l3addr_reachable";
51 private static final String VM_CHANGED = "vm_state_changed";
52 private static final Register REGISTRATION = new Register("register",
53 List.of(VM_CHANGED, HOST_REACHABLE, HOST_UNREACHABLE));
54 private static final String WS_PATH = "ws/event";
56 private final Logger logger = LoggerFactory.getLogger(WebSocketManager.class);
57 private final Map<MACAddress, HostHandler> lanHosts = new HashMap<>();
58 private final Map<Integer, VmHandler> vms = new HashMap<>();
59 private final ApiHandler apiHandler;
61 private volatile @Nullable Session wsSession;
63 private record Register(String action, List<String> events) {
67 public WebSocketManager(FreeboxOsSession session) throws FreeboxException {
68 super(session, LoginManager.Permission.NONE, session.getUriBuilder().path(WS_PATH));
69 this.apiHandler = session.getApiHandler();
72 private static enum Action {
78 private static record WebSocketResponse(boolean success, Action action, String event, String source,
79 @Nullable JsonElement result) {
80 public String getEvent() {
81 return source + "_" + event;
85 public void openSession(@Nullable String sessionToken) throws FreeboxException {
86 WebSocketClient client = new WebSocketClient(apiHandler.getHttpClient());
87 URI uri = getUriBuilder().scheme(getUriBuilder().build().getScheme().contains("s") ? "wss" : "ws").build();
88 ClientUpgradeRequest request = new ClientUpgradeRequest();
89 request.setHeader(ApiHandler.AUTH_HEADER, sessionToken);
93 client.connect(this, uri, request);
94 } catch (Exception e) {
95 throw new FreeboxException(e, "Exception connecting websocket client");
99 public void closeSession() {
100 logger.debug("Awaiting closure from remote");
101 Session localSession = wsSession;
102 if (localSession != null) {
103 localSession.close();
108 public void onWebSocketConnect(@NonNullByDefault({}) Session wsSession) {
109 this.wsSession = wsSession;
110 logger.debug("Websocket connection establisehd");
112 wsSession.getRemote().sendString(apiHandler.serialize(REGISTRATION));
113 } catch (IOException e) {
114 logger.warn("Error connecting to websocket: {}", e.getMessage());
119 public void onWebSocketText(@NonNullByDefault({}) String message) {
120 Session localSession = wsSession;
121 if (message.toLowerCase(Locale.US).contains("bye") && localSession != null) {
122 localSession.close(StatusCode.NORMAL, "Thanks");
126 WebSocketResponse result = apiHandler.deserialize(WebSocketResponse.class, message);
127 if (result.success) {
128 switch (result.action) {
130 logger.debug("Event registration successfull");
133 handleNotification(result);
136 logger.warn("Unhandled notification received: {}", result.action);
141 private void handleNotification(WebSocketResponse result) {
142 JsonElement json = result.result;
144 switch (result.getEvent()) {
146 VirtualMachine vm = apiHandler.deserialize(VirtualMachine.class, json.toString());
147 logger.debug("Received notification for VM {}", vm.id());
148 VmHandler vmHandler = vms.get(vm.id());
149 if (vmHandler != null) {
150 vmHandler.updateVmChannels(vm);
153 case HOST_UNREACHABLE, HOST_REACHABLE:
154 LanHost host = apiHandler.deserialize(LanHost.class, json.toString());
155 MACAddress mac = host.getMac();
156 logger.debug("Received notification for LanHost {}", mac.toColonDelimitedString());
157 HostHandler hostHandler = lanHosts.get(mac);
158 if (hostHandler != null) {
159 hostHandler.updateConnectivityChannels(host);
163 logger.warn("Unhandled event received: {}", result.getEvent());
166 logger.warn("Empty json element in notification");
171 public void onWebSocketClose(int statusCode, @NonNullByDefault({}) String reason) {
172 logger.debug("Socket Closed: [{}] - reason {}", statusCode, reason);
173 this.wsSession = null;
177 public void onWebSocketError(@NonNullByDefault({}) Throwable cause) {
178 logger.warn("Error on websocket: {}", cause.getMessage());
182 public void onWebSocketBinary(byte @Nullable [] payload, int offset, int len) {
186 public void registerListener(MACAddress mac, HostHandler hostHandler) {
187 lanHosts.put(mac, hostHandler);
190 public void unregisterListener(MACAddress mac) {
191 lanHosts.remove(mac);
194 public void registerVm(int clientId, VmHandler vmHandler) {
195 vms.put(clientId, vmHandler);
198 public void unregisterVm(int clientId) {
199 vms.remove(clientId);