Java tutorial
/** * Copyright (c) 2010-2019 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 */ package org.openhab.binding.max.internal.message; import static org.openhab.binding.max.internal.MaxBindingConstants.*; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.commons.net.util.Base64; import org.openhab.binding.max.internal.Utils; import org.openhab.binding.max.internal.device.DeviceType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The {@link CMessage} contains configuration about a MAX! device. * * @author Andreas Heil (info@aheil.de) - Initial contribution * @author Marcel Verpaalen - Detailed parsing, OH2 Update */ public final class CMessage extends Message { private final Logger logger = LoggerFactory.getLogger(CMessage.class); private String rfAddress; private int length; private DeviceType deviceType; private int roomId = -1; private String serialNumber; private BigDecimal tempComfort; private BigDecimal tempEco; private BigDecimal tempSetpointMax; private BigDecimal tempSetpointMin; private BigDecimal tempOffset; private BigDecimal tempOpenWindow; private BigDecimal durationOpenWindow; private BigDecimal decalcification; private BigDecimal valveMaximum; private BigDecimal valveOffset; private BigDecimal boostDuration; private BigDecimal boostValve; private String programData; private Map<String, Object> properties = new HashMap<>(); public CMessage(String raw) { super(raw); String[] tokens = this.getPayload().split(Message.DELIMETER); rfAddress = tokens[0]; byte[] bytes = Base64.decodeBase64(tokens[1].getBytes(StandardCharsets.UTF_8)); int[] data = new int[bytes.length]; for (int i = 0; i < bytes.length; i++) { data[i] = bytes[i] & 0xFF; } length = data[0]; if (length != data.length - 1) { logger.debug("C Message malformed: wrong data length. Expected bytes {}, actual bytes {}", length, data.length - 1); } String rfAddress2 = Utils.toHex(data[1], data[2], data[3]); if (!rfAddress.toUpperCase().equals(rfAddress2.toUpperCase())) { logger.debug("C Message malformed: wrong RF address. Expected address {}, actual address {}", rfAddress.toUpperCase(), rfAddress2.toUpperCase()); } deviceType = DeviceType.create(data[4]); roomId = data[5] & 0xFF; serialNumber = getSerialNumber(bytes); if (deviceType == DeviceType.HeatingThermostatPlus || deviceType == DeviceType.HeatingThermostat || deviceType == DeviceType.WallMountedThermostat) { parseHeatingThermostatData(bytes); } if (deviceType == DeviceType.Cube) { parseCubeData(bytes); } if (deviceType == DeviceType.EcoSwitch || deviceType == DeviceType.ShutterContact) { logger.trace("Device {} type {} Data: '{}'", rfAddress, deviceType, parseData(bytes)); } } private String getSerialNumber(byte[] bytes) { byte[] sn = new byte[10]; for (int i = 0; i < 10; i++) { sn[i] = bytes[i + 8]; } return new String(sn, StandardCharsets.UTF_8); } private String parseData(byte[] bytes) { if (bytes.length <= 18) { return ""; } try { int dataStart = 18; byte[] sn = new byte[bytes.length - dataStart]; for (int i = 0; i < sn.length; i++) { sn[i] = bytes[i + dataStart]; } logger.trace("DataBytes: {}", Utils.getHex(sn)); return new String(sn, StandardCharsets.UTF_8); } catch (Exception e) { logger.debug("Exception occurred during parsing: {}", e.getMessage(), e); } return ""; } private void parseCubeData(byte[] bytes) { if (bytes.length != 238) { logger.debug("Unexpected lenght for Cube message {}, expected: 238", bytes.length); } try { properties.put("Portal Enabled", Integer.toString(bytes[0x18] & 0xFF)); properties.put("Portal URL", new String(Arrays.copyOfRange(bytes, 0x55, 0xD5), StandardCharsets.UTF_8).trim()); properties.put("TimeZone (Winter)", new String(Arrays.copyOfRange(bytes, 0xD6, 0xDA), StandardCharsets.UTF_8).trim()); properties.put("TimeZone (Daylight)", new String(Arrays.copyOfRange(bytes, 0x00E2, 0x00E6), StandardCharsets.UTF_8).trim()); properties.put("Unknown1", Utils.getHex(Arrays.copyOfRange(bytes, 0x13, 0x33))); // Pushbutton Up config // 0=auto, 1=eco, 2=comfort properties.put("Unknown2", Utils.getHex(Arrays.copyOfRange(bytes, 0x34, 0x54))); // Pushbutton down config // 0=auto, 1=eco, 2=comfort properties.put("Winter Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xDB, 0xE2), "Winter").toString()); // Date // of // wintertime properties.put("Summer Time", parseTimeInfo(Arrays.copyOfRange(bytes, 0xE7, 0xEF), "Summer").toString()); // Date // of // summertime } catch (Exception e) { logger.debug("Exception occurred during parsing: {}", e.getMessage(), e); } } private Date parseTimeInfo(byte[] bytes, String suffix) { int month = bytes[0] & 0xFF; int weekDay = bytes[1] & 0xFF; int hour = bytes[2] & 0xFF; int utcOffset = new BigInteger(Arrays.copyOfRange(bytes, 0x03, 0x07)).intValue(); suffix = " (" + suffix + ")"; properties.put("Utc Offset" + suffix, utcOffset); Calendar pCal = Calendar.getInstance(); pCal.set(Calendar.getInstance().get(Calendar.YEAR), month - 1, 15, hour, 0, 0); pCal.set(Calendar.DAY_OF_WEEK, weekDay + 1); pCal.set(Calendar.DAY_OF_WEEK_IN_MONTH, -1); return pCal.getTime(); } private void parseHeatingThermostatData(byte[] bytes) { try { int plusDataStart = 18; int programDataStart = 11; tempComfort = new BigDecimal((bytes[plusDataStart] & 0xFF) / 2D); tempEco = new BigDecimal((bytes[plusDataStart + 1] & 0xFF) / 2D); tempSetpointMax = new BigDecimal((bytes[plusDataStart + 2] & 0xFF) / 2D); tempSetpointMin = new BigDecimal((bytes[plusDataStart + 3] & 0xFF) / 2D); properties.put(PROPERTY_THERMO_COMFORT_TEMP, tempComfort.setScale(1, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_ECO_TEMP, tempEco.setScale(1, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_MAX_TEMP_SETPOINT, tempSetpointMax.setScale(1, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_MIN_TEMP_SETPOINT, tempSetpointMin.setScale(1, RoundingMode.HALF_DOWN)); if (bytes.length < 211) { // Device is a WallMountedThermostat programDataStart = 4; logger.trace("WallThermostat byte {}: {}", bytes.length - 3, Float.toString(bytes[bytes.length - 3] & 0xFF)); logger.trace("WallThermostat byte {}: {}", bytes.length - 2, Float.toString(bytes[bytes.length - 2] & 0xFF)); logger.trace("WallThermostat byte {}: {}", bytes.length - 1, Float.toString(bytes[bytes.length - 1] & 0xFF)); } else { // Device is a HeatingThermostat(+) tempOffset = new BigDecimal((bytes[plusDataStart + 4] & 0xFF) / 2D - 3.5); tempOpenWindow = new BigDecimal((bytes[plusDataStart + 5] & 0xFF) / 2D); durationOpenWindow = new BigDecimal((bytes[plusDataStart + 6] & 0xFF) * 5); boostDuration = new BigDecimal(bytes[plusDataStart + 7] & 0xFF >> 5); boostValve = new BigDecimal((bytes[plusDataStart + 7] & 0x1F) * 5); decalcification = new BigDecimal(bytes[plusDataStart + 8]); valveMaximum = new BigDecimal((bytes[plusDataStart + 9] & 0xFF) * 100 / 255); valveOffset = new BigDecimal((bytes[plusDataStart + 10] & 0xFF) * 100 / 255); properties.put(PROPERTY_THERMO_OFFSET_TEMP, tempOffset.setScale(1, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_WINDOW_OPEN_TEMP, tempOpenWindow.setScale(1, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_WINDOW_OPEN_DURATION, durationOpenWindow.setScale(0, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_BOOST_DURATION, boostDuration.setScale(0, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_BOOST_VALVEPOS, boostValve.setScale(0, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_DECALCIFICATION, decalcification.setScale(0, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_VALVE_MAX, valveMaximum.setScale(0, RoundingMode.HALF_DOWN)); properties.put(PROPERTY_THERMO_VALVE_OFFSET, valveOffset.setScale(0, RoundingMode.HALF_DOWN)); } programData = ""; int ln = 13 * 6; // first day = Sat String startTime = "00:00h"; for (int charIdx = plusDataStart + programDataStart; charIdx < (plusDataStart + programDataStart + 26 * 7); charIdx++) { if (ln % 13 == 0) { programData += "\r\n Day " + Integer.toString((ln / 13) % 7) + ": "; startTime = "00:00h"; } int progTime = (bytes[charIdx + 1] & 0xFF) * 5 + (bytes[charIdx] & 0x01) * 1280; int progMinutes = progTime % 60; int progHours = (progTime - progMinutes) / 60; String endTime = Integer.toString(progHours) + ":" + String.format("%02d", progMinutes) + "h"; programData += startTime + "-" + endTime + " " + Double.toString(bytes[charIdx] / 4) + "C "; startTime = endTime; charIdx++; ln++; } } catch (Exception e) { logger.debug("Exception occurred during heater data: {}", e.getMessage(), e); } return; } public String getSerialNumber() { return serialNumber; } @Override public MessageType getType() { return MessageType.C; } public String getRFAddress() { return rfAddress; } public DeviceType getDeviceType() { return deviceType; } public Map<String, Object> getProperties() { return properties; } public int getRoomID() { return roomId; } @Override public void debug(Logger logger) { logger.debug("=== C Message === "); logger.trace("\tRAW: {}", this.getPayload()); logger.debug("DeviceType: {}", deviceType); logger.debug("SerialNumber: {}", serialNumber); logger.debug("RFAddress: {}", rfAddress); logger.debug("RoomID: {}", roomId); for (String key : properties.keySet()) { if (!key.startsWith("Unknown")) { String propertyName = StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(key), ' '); logger.debug("{}: {}", propertyName, properties.get(key)); } else { logger.debug("{}: {}", key, properties.get(key)); } } if (programData != null) { logger.trace("ProgramData: {}", programData); } } }