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.energenie.internal.handler;
15 import static org.openhab.binding.energenie.internal.EnergenieBindingConstants.*;
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;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.core.util.HexUtils;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * Class handling the Socket connections.
33 * @author Hans-Jörg Merk - Initial contribution
34 * @author Hilbrand Bouwkamp - Moved Socket to it's own class
37 class EnergenieSocket {
39 private static final int SOCKET_TIMEOUT_MILLISECONDS = 1500;
40 private static final byte[] MESSAGE = { 0x11 };
42 private final Logger logger = LoggerFactory.getLogger(EnergenieSocket.class);
43 private final String host;
44 private final byte[] key;
46 public EnergenieSocket(final String host, final String password) {
48 this.key = getKey(password);
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 + " ";
57 return passwordString.getBytes();
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());
66 throw new IOException("No connection");
68 if (logger.isTraceEnabled()) {
69 logger.trace("Control message send to EG (int) '{}' (hex)'{}'", ctrl, HexUtils.bytesToHex(ctrl));
71 output.write(encryptControls(ctrl, taskSocket.task));
73 readStatus(input, taskSocket);
74 return updateStatus(taskSocket);
79 public synchronized byte[] retrieveStatus() throws IOException {
80 try (final TaskSocket taskSocket = authorize()) {
81 return updateStatus(taskSocket);
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());
90 throw new IOException("No connection");
92 output.write(MESSAGE);
94 logger.trace("Start Condition '{}' send to EG", MESSAGE);
95 input.readFully(taskSocket.task);
97 if (logger.isTraceEnabled()) {
98 logger.trace("EG responded with task (int) '{}' (hex) '{}'", taskSocket.task,
99 HexUtils.bytesToHex(taskSocket.task));
101 final byte[] solutionMessage = calculateSolution(taskSocket.task);
103 output.write(solutionMessage);
105 logger.trace("Solution '{}' send to EG", solutionMessage);
106 readStatus(input, taskSocket);
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));
118 private byte[] updateStatus(final TaskSocket taskSocket) throws IOException {
119 final byte[] status = decryptStatus(taskSocket);
121 if (logger.isTraceEnabled()) {
122 logger.trace("EG responded with status (int) '{}' (hex) '{}'", status, HexUtils.bytesToHex(status));
127 private byte[] calculateSolution(final byte[] task) {
128 final int[] uIntTask = new int[4];
130 for (int i = 0; i < 4; i++) {
131 uIntTask[i] = Byte.toUnsignedInt(task[i]);
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();
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];
140 solution[0] = loword[3];
141 solution[1] = loword[2];
142 solution[2] = hiword[3];
143 solution[3] = hiword[2];
148 private byte[] decryptStatus(final TaskSocket taskSocket) {
149 final byte[] status = new byte[4];
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]);
158 private byte[] encryptControls(final byte[] controls, final byte[] task) {
159 final byte[] ctrlcryp = new byte[CTRLCRYP_LEN];
161 for (int i = 0; i < 4; i++) {
162 ctrlcryp[i] = (byte) ((((controls[3 - i] ^ task[2]) + task[3]) ^ key[0]) + key[1]);
167 private class TaskSocket implements Closeable {
169 final byte[] task = new byte[TASK_LEN];
170 final byte[] statcryp = new byte[STATCRYP_LEN];
172 public TaskSocket() throws UnknownHostException, IOException {
173 socket = new Socket(host, TCP_PORT);
174 socket.setSoTimeout(SOCKET_TIMEOUT_MILLISECONDS);
178 public void close() throws IOException {