]> git.basschouten.com Git - openhab-addons.git/blob
6e29c04082e6c830caced4fc2e3631b258af2ace
[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.energenie.internal.handler;
14
15 import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
16
17 import java.io.Closeable;
18 import java.io.DataInputStream;
19 import java.io.IOException;
20 import java.io.OutputStream;
21 import java.net.Socket;
22 import java.net.UnknownHostException;
23 import java.nio.ByteBuffer;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.core.util.HexUtils;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Class handling the Socket connections.
32  *
33  * @author Hans-Jörg Merk - Initial contribution
34  * @author Hilbrand Bouwkamp - Moved Socket to it's own class
35  */
36 @NonNullByDefault
37 class EnergenieSocket {
38
39     private static final int SOCKET_TIMEOUT_MILLISECONDS = 1500;
40     private static final byte[] MESSAGE = { 0x11 };
41
42     private final Logger logger = LoggerFactory.getLogger(EnergenieSocket.class);
43     private final String host;
44     private final byte[] key;
45
46     public EnergenieSocket(final String host, final String password) {
47         this.host = host;
48         this.key = getKey(password);
49     }
50
51     private static byte[] getKey(final String password) {
52         final int passwordLength = password.length();
53         String passwordString = password;
54         for (int i = 0; i < (8 - passwordLength); i++) {
55             passwordString = passwordString + " ";
56         }
57         return passwordString.getBytes();
58     }
59
60     public synchronized byte[] sendCommand(final byte[] ctrl) throws IOException {
61         try (final TaskSocket taskSocket = authorize()) {
62             final OutputStream output = taskSocket.socket.getOutputStream();
63             final DataInputStream input = new DataInputStream(taskSocket.socket.getInputStream());
64
65             if (output == null) {
66                 throw new IOException("No connection");
67             } else {
68                 if (logger.isTraceEnabled()) {
69                     logger.trace("Control message send to EG (int) '{}' (hex)'{}'", ctrl, HexUtils.bytesToHex(ctrl));
70                 }
71                 output.write(encryptControls(ctrl, taskSocket.task));
72                 output.flush();
73                 readStatus(input, taskSocket);
74                 return updateStatus(taskSocket);
75             }
76         }
77     }
78
79     public synchronized byte[] retrieveStatus() throws IOException {
80         try (final TaskSocket taskSocket = authorize()) {
81             return updateStatus(taskSocket);
82         }
83     }
84
85     private TaskSocket authorize() throws IOException {
86         final TaskSocket taskSocket = new TaskSocket();
87         final OutputStream output = taskSocket.socket.getOutputStream();
88         final DataInputStream input = new DataInputStream(taskSocket.socket.getInputStream());
89         if (output == null) {
90             throw new IOException("No connection");
91         }
92         output.write(MESSAGE);
93         output.flush();
94         logger.trace("Start Condition '{}' send to EG", MESSAGE);
95         input.readFully(taskSocket.task);
96
97         if (logger.isTraceEnabled()) {
98             logger.trace("EG responded with task (int) '{}' (hex) '{}'", taskSocket.task,
99                     HexUtils.bytesToHex(taskSocket.task));
100         }
101         final byte[] solutionMessage = calculateSolution(taskSocket.task);
102
103         output.write(solutionMessage);
104         output.flush();
105         logger.trace("Solution '{}' send to EG", solutionMessage);
106         readStatus(input, taskSocket);
107         return taskSocket;
108     }
109
110     private void readStatus(final DataInputStream input, final TaskSocket taskSocket) throws IOException {
111         input.readFully(taskSocket.statcryp);
112         if (logger.isTraceEnabled()) {
113             logger.trace("EG responded with statcryp (int) '{}' (hex) '{}'", taskSocket.statcryp,
114                     HexUtils.bytesToHex(taskSocket.statcryp));
115         }
116     }
117
118     private byte[] updateStatus(final TaskSocket taskSocket) throws IOException {
119         final byte[] status = decryptStatus(taskSocket);
120
121         if (logger.isTraceEnabled()) {
122             logger.trace("EG responded with status (int) '{}' (hex) '{}'", status, HexUtils.bytesToHex(status));
123         }
124         return status;
125     }
126
127     private byte[] calculateSolution(final byte[] task) {
128         final int[] uIntTask = new int[4];
129
130         for (int i = 0; i < 4; i++) {
131             uIntTask[i] = Byte.toUnsignedInt(task[i]);
132         }
133         final int solutionLoword = (((uIntTask[0] ^ key[2]) * key[0]) ^ (key[6] | (key[4] << 8)) ^ uIntTask[2]);
134         final byte[] loword = ByteBuffer.allocate(4).putInt(solutionLoword).array();
135
136         final int solutionHiword = (((uIntTask[1] ^ key[3]) * key[1]) ^ (key[7] | (key[5] << 8)) ^ uIntTask[3]);
137         final byte[] hiword = ByteBuffer.allocate(4).putInt(solutionHiword).array();
138         final byte[] solution = new byte[SOLUTION_LEN];
139
140         solution[0] = loword[3];
141         solution[1] = loword[2];
142         solution[2] = hiword[3];
143         solution[3] = hiword[2];
144
145         return solution;
146     }
147
148     private byte[] decryptStatus(final TaskSocket taskSocket) {
149         final byte[] status = new byte[4];
150
151         for (int i = 0; i < 4; i++) {
152             status[i] = (byte) ((((taskSocket.statcryp[3 - i] - key[1]) ^ key[0]) - taskSocket.task[3])
153                     ^ taskSocket.task[2]);
154         }
155         return status;
156     }
157
158     private byte[] encryptControls(final byte[] controls, final byte[] task) {
159         final byte[] ctrlcryp = new byte[CTRLCRYP_LEN];
160
161         for (int i = 0; i < 4; i++) {
162             ctrlcryp[i] = (byte) ((((controls[3 - i] ^ task[2]) + task[3]) ^ key[0]) + key[1]);
163         }
164         return ctrlcryp;
165     }
166
167     private class TaskSocket implements Closeable {
168         final Socket socket;
169         final byte[] task = new byte[TASK_LEN];
170         final byte[] statcryp = new byte[STATCRYP_LEN];
171
172         public TaskSocket() throws UnknownHostException, IOException {
173             socket = new Socket(host, TCP_PORT);
174             socket.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
175         }
176
177         @Override
178         public void close() throws IOException {
179             socket.close();
180         }
181     }
182 }