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.max.internal.command;
15 import java.io.ByteArrayOutputStream;
16 import java.io.IOException;
17 import java.nio.charset.StandardCharsets;
18 import java.util.ArrayList;
19 import java.util.Base64;
20 import java.util.List;
22 import java.util.TreeSet;
24 import org.apache.commons.lang3.StringUtils;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.binding.max.internal.Utils;
27 import org.openhab.binding.max.internal.device.Device;
28 import org.openhab.binding.max.internal.device.RoomInformation;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
33 * The {@link MCommand} Creates the MAX! Cube the room & device name information update message.
35 * @author Marcel Verpaalen - Initial Contribution
38 public class MCommand extends CubeCommand {
39 private final Logger logger = LoggerFactory.getLogger(MCommand.class);
41 private static final byte MAGIC_NR = 86;
42 private static final byte M_VERSION = 2;
43 private static final int MAX_NAME_LENGTH = 32;
44 private static final int MAX_GROUP_COUNT = 20;
45 private static final int MAX_DEVICES_COUNT = 140;
46 private static final int MAX_MSG_LENGTH = 1900;
48 private final List<Device> devices;
49 public final List<RoomInformation> rooms;
51 public MCommand(List<Device> devices) {
52 this(devices, new ArrayList<>());
55 public MCommand(List<Device> devices, List<RoomInformation> rooms) {
56 this.devices = new ArrayList<>(devices);
57 this.rooms = new ArrayList<>(rooms);
61 public void listRooms() {
62 for (RoomInformation room : rooms) {
63 logger.debug("M Command room info: {}", room);
67 public List<RoomInformation> getRooms() {
71 private void roombuilder() {
72 for (Device di : devices) {
73 boolean foundRoom = false;
75 for (RoomInformation room : rooms) {
76 if (room.getPosition() == di.getRoomId()) {
80 // Add new rooms based on device information.
81 // TODO check if it is allowed to have any device creating a room, or should it be a thermostat
82 if (!foundRoom && di.getRoomId() != 0 && rooms.size() < MAX_GROUP_COUNT) {
83 RoomInformation room = new RoomInformation(di.getRoomId(), di.getRoomName(), di.getRFAddress());
89 public byte[] concatenateByteArrays(List<byte[]> blocks) {
90 ByteArrayOutputStream os = new ByteArrayOutputStream();
91 for (byte[] b : blocks) {
92 os.write(b, 0, b.length);
94 return os.toByteArray();
98 public String getCommandString() {
102 ByteArrayOutputStream message = new ByteArrayOutputStream();
105 byte[] header = { MAGIC_NR, M_VERSION, (byte) rooms.size() };
106 message.write(header);
108 Set<Integer> sortedRooms = new TreeSet<>();
109 for (RoomInformation room : rooms) {
110 sortedRooms.add(room.getPosition());
113 for (Integer roomPos : sortedRooms) {
114 for (RoomInformation room : rooms) {
115 if (room.getPosition() == roomPos) {
116 if (roomCount < MAX_GROUP_COUNT) {
117 byte[] roomName = StringUtils.abbreviate(room.getName(), MAX_NAME_LENGTH)
118 .getBytes(StandardCharsets.UTF_8);
119 byte[] nameLength = new byte[] { (byte) roomName.length };
120 byte[] rfAddress = Utils.hexStringToByteArray(room.getRFAddress());
121 message.write(roomPos.byteValue());
122 message.write(nameLength);
123 message.write(roomName);
124 message.write(rfAddress);
126 logger.warn("{} exceeds max number of rooms ({}). Ignored", room, MAX_GROUP_COUNT);
132 for (Device di : devices) {
133 if (deviceCount < MAX_DEVICES_COUNT) {
136 logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
139 message.write((byte) deviceCount);
140 for (Device di : devices) {
141 if (deviceCount > 0) {
142 byte[] deviceType = { (byte) di.getType().getValue() };
143 byte[] rfAddress = Utils.hexStringToByteArray(di.getRFAddress());
144 byte[] deviceName = StringUtils.abbreviate(di.getName(), MAX_NAME_LENGTH)
145 .getBytes(StandardCharsets.UTF_8);
146 byte[] nameLength = { (byte) deviceName.length };
147 byte[] serialNumber = di.getSerialNumber().getBytes(StandardCharsets.UTF_8);
148 byte[] roomId = { (byte) di.getRoomId() };
150 message.write(deviceType);
151 message.write(rfAddress);
152 message.write(serialNumber);
153 message.write(nameLength);
154 message.write(deviceName);
155 message.write(roomId);
157 logger.warn("{} exceeds max number of devices ({}). Ignored", di, MAX_DEVICES_COUNT);
162 byte[] dst = { 0x01 };
164 } catch (IOException e) {
165 logger.debug("Error while generating m: command: {}", e.getMessage(), e);
168 final String encodedString = Base64.getEncoder().encodeToString(message.toByteArray());
169 final StringBuilder commandStringBuilder = new StringBuilder();
170 int parts = (int) Math.round(encodedString.length() / MAX_MSG_LENGTH + 0.5);
171 for (int i = 0; i < parts; i++) {
172 String partString = StringUtils.abbreviate(encodedString.substring((i) * MAX_MSG_LENGTH), MAX_MSG_LENGTH);
173 commandStringBuilder.append("m:").append(String.format("%02d", i)).append(",").append(partString)
174 .append('\r').append('\n');
177 return commandStringBuilder.toString();
181 public String getReturnStrings() {