/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.binding.maxcube.internal.message; import java.util.Date; import java.util.List; import org.openhab.binding.maxcube.internal.Utils; import org.openhab.binding.maxcube.internal.message.Battery.Charge; import org.openhab.core.library.types.OpenClosedType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for devices provided by the MAX!Cube protocol. * * @author Andreas Heil (info@aheil.de) * @author Bernd Michael Helm (bernd.helm at helmundwalter.de) * @since 1.4.0 */ public abstract class Device { protected final static Logger logger = LoggerFactory.getLogger(Device.class); private final String serialNumber; private final String rfAddress; private final int roomId; private final Battery battery = new Battery(); private boolean initialized; private boolean answer; private Boolean error = null; private boolean errorUpdated; private boolean valid; private boolean DstSettingsActive; private boolean gatewayKnown; private boolean panelLocked; private boolean linkStatusError; public Device(Configuration c) { this.serialNumber = c.getSerialNumber(); this.rfAddress = c.getRFAddress(); this.roomId = c.getRoomId(); } public abstract DeviceType getType(); private static Device create(String rfAddress, List<Configuration> configurations) { Device returnValue = null; for (Configuration c : configurations) { if (c.getRFAddress().toUpperCase().equals(rfAddress.toUpperCase())) { switch (c.getDeviceType()) { case HeatingThermostatPlus: case HeatingThermostat: HeatingThermostat thermostat = new HeatingThermostat(c); thermostat.setType(c.getDeviceType()); return thermostat; case EcoSwitch: return new EcoSwitch(c); case ShutterContact: return new ShutterContact(c); case WallMountedThermostat: return new WallMountedThermostat(c); default: return new UnsupportedDevice(c); } } } return returnValue; } public static Device create(byte[] raw, List<Configuration> configurations) { if (raw.length == 0) { return null; } String rfAddress = Utils.toHex(raw[0] & 0xFF, raw[1] & 0xFF, raw[2] & 0xFF); // Based on the RF address and the corresponding configuration, // create the device based on the type specified in its configuration Device device = Device.create(rfAddress, configurations); if (device == null) { logger.warn("Can't create device from received message, returning NULL."); return null; } return Device.update(raw, configurations, device); } public static Device update(byte[] raw, List<Configuration> configurations, Device device) { String rfAddress = device.getRFAddress(); // byte 4 is skipped // multiple device information are encoded in those particular bytes boolean[] bits1 = Utils.getBits(Utils.fromByte(raw[4])); boolean[] bits2 = Utils.getBits(Utils.fromByte(raw[5])); device.setInitialized(bits1[1]); device.setAnswer(bits1[2]); device.setError(bits1[3]); device.setValid(bits1[4]); device.setDstSettingActive(bits2[3]); device.setGatewayKnown(bits2[4]); device.setPanelLocked(bits2[5]); device.setLinkStatusError(bits2[6]); device.battery().setCharge(bits2[7] ? Charge.LOW : Charge.OK); logger.trace("Device {} L Message length: {} content: {}", rfAddress, raw.length, Utils.getHex(raw)); // TODO move the device specific readings into the sub classes switch (device.getType()) { case WallMountedThermostat: case HeatingThermostat: case HeatingThermostatPlus: HeatingThermostat heatingThermostat = (HeatingThermostat) device; // "xxxx xx00 = automatic, xxxx xx01 = manual, xxxx xx10 = vacation, xxxx xx11 = boost": if (bits2[1] == false && bits2[0] == false) { heatingThermostat.setMode(ThermostatModeType.AUTOMATIC); } else if (bits2[1] == false && bits2[0] == true) { heatingThermostat.setMode(ThermostatModeType.MANUAL); } else if (bits2[1] == true && bits2[0] == false) { heatingThermostat.setMode(ThermostatModeType.VACATION); } else if (bits2[1] == true && bits2[0] == true) { heatingThermostat.setMode(ThermostatModeType.BOOST); } else { // TODO: handel malformed message } heatingThermostat.setValvePosition(raw[6] & 0xFF); heatingThermostat.setTemperatureSetpoint(raw[7] & 0x7F); // 9 2 858B Date until (05-09-2011) (see Encoding/Decoding // date/time) // B 1 2E Time until (23:00) (see Encoding/Decoding date/time) String hexDate = Utils.toHex(raw[8] & 0xFF, raw[9] & 0xFF); int dateValue = Utils.fromHex(hexDate); int timeValue = raw[10] & 0xFF; Date date = Utils.resolveDateTime(dateValue, timeValue); heatingThermostat.setDateSetpoint(date); int actualTemp = 0; if (device.getType() == DeviceType.WallMountedThermostat) { actualTemp = (raw[11] & 0xFF) + (raw[7] & 0x80) * 2; } else { if (heatingThermostat.getMode() != ThermostatModeType.VACATION && heatingThermostat.getMode() != ThermostatModeType.BOOST) { actualTemp = (raw[8] & 0xFF) * 256 + (raw[9] & 0xFF); } else { logger.debug("No temperature reading in {} mode", heatingThermostat.getMode()); } } if (actualTemp != 0) { logger.debug("Actual Temperature : {}", (double) actualTemp / 10); heatingThermostat.setTemperatureActual((double) actualTemp / 10); } break; case EcoSwitch: String eCoSwitchData = Utils.toHex(raw[3] & 0xFF, raw[4] & 0xFF, raw[5] & 0xFF); logger.trace("EcoSwitch Device {} status bytes : {}", rfAddress, eCoSwitchData); case ShutterContact: ShutterContact shutterContact = (ShutterContact) device; // xxxx xx10 = shutter open, xxxx xx00 = shutter closed if (bits2[1] == true && bits2[0] == false) { shutterContact.setShutterState(OpenClosedType.OPEN); logger.trace("Device {} status: Open", rfAddress); } else if (bits2[1] == false && bits2[0] == false) { shutterContact.setShutterState(OpenClosedType.CLOSED); logger.trace("Device {} status: Closed", rfAddress); } else { logger.trace("Device {} status switch status Unknown (true-true)", rfAddress); } break; default: logger.debug("Unhandled Device. DataBytes: " + Utils.getHex(raw)); break; } return device; } public Battery battery() { return battery; } public final String getRFAddress() { return this.rfAddress; } public final int getRoomId() { return roomId; } private void setLinkStatusError(boolean linkStatusError) { this.linkStatusError = linkStatusError; } private void setPanelLocked(boolean panelLocked) { this.panelLocked = panelLocked; } private void setGatewayKnown(boolean gatewayKnown) { this.gatewayKnown = gatewayKnown; } private void setDstSettingActive(boolean dstSettingsActive) { this.DstSettingsActive = dstSettingsActive; } private void setValid(boolean valid) { this.valid = valid; } protected void setError(boolean newError) { errorUpdated = (this.error == null) || (this.error != newError); this.error = newError; if (newError) { logger.warn("Connection error occurred between cube and device '{}'", this.toString()); } } public boolean isError() { return Boolean.TRUE.equals(error); } public boolean isErrorUpdated() { return errorUpdated; } public String getSerialNumber() { return serialNumber; } private void setInitialized(boolean initialized) { this.initialized = initialized; } private void setAnswer(boolean answer) { this.answer = answer; } @Override public String toString() { return rfAddress + " - " + serialNumber; } }