/**
* 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.plugwise.protocol;
import java.io.UnsupportedEncodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class to represent Plugwise protocol data units
*
* In general a message consists of a hex string containing the following parts:
* a type indicator - many types are yet to be reverse engineered
* a sequence number - messages in PW are numbered so that we can keep track of them in an application
* a PW MAC address - basically, the destination of the message
* a payload
* a CRC checksum that is calculated using the previously mentioned segments of the message
*
* Before sending off a message in the PW network they are prepended with a protocol header and trailer is
* added at the end
*
*
* @author Karel Goderis
* @since 1.1.0
*/
public abstract class Message {
protected static final Logger logger = LoggerFactory.getLogger(Message.class);
protected MessageType type;
protected int sequenceNumber = 0;
protected String payLoad;
protected String MAC;
// to keep track how many attempts we have made to send this message
protected int retryCount = 0;
// for messages where the MAC address is part of the payload
public Message(int sequenceNumber, String payLoad) {
this.sequenceNumber = sequenceNumber;
this.payLoad = payLoad;
parsePayLoad();
}
@Override
public String toString() {
return "Plugwise Message [type=" + type.toString() + ", MAC=" + MAC + ", SeqNr=" + sequenceNumber + ", payload="
+ payLoad + ", retrycount=" + retryCount + "]";
}
// for messages where we do not care about the MAC address
public Message(String payLoad) {
this.payLoad = payLoad;
if (payLoad != null) {
parsePayLoad();
}
}
public Message(int sequenceNumber, String MAC, String payLoad) {
this.payLoad = payLoad;
this.sequenceNumber = sequenceNumber;
this.MAC = MAC;
if (payLoad != null) {
parsePayLoad();
}
}
// for messages where we do not care about the sequence number
public Message(String MAC, String payLoad) {
this.payLoad = payLoad;
this.MAC = MAC;
if (payLoad != null) {
parsePayLoad();
}
}
public void increaseAttempts() {
retryCount++;
}
public int getAttempts() {
return retryCount;
}
public MessageType getType() {
return type;
}
public int getSequenceNumber() {
return sequenceNumber;
}
public void setSequenceNumber(int sequenceNumber) {
this.sequenceNumber = sequenceNumber;
}
public String getPayLoad() {
return payLoad;
}
public String getMAC() {
return MAC;
}
protected String sequenceNumberToHexString() {
return String.format("%04X", sequenceNumber);
}
abstract protected String payLoadToHexString();
private String MACToHexString() {
return getMAC();
}
private String typeToHexString() {
return String.format("%04X", type.toInt());
}
// Method that implementation classes have to override, and that is responsible for parsing the payload into
// meaningful fields
abstract protected void parsePayLoad();
public String toHexString() {
StringBuilder sb = new StringBuilder();
sb.append(typeToHexString());
sb.append(sequenceNumberToHexString());
sb.append(MACToHexString());
sb.append(payLoadToHexString());
String result = sb.toString();
String CRC = getCRC(result);
result = result + CRC;
return result;
}
protected String getCRC(String buffer) {
int crc = 0x0000;
int polynomial = 0x1021; // 0001 0000 0010 0001 (0, 5, 12)
byte[] bytes = new byte[0];
try {
bytes = buffer.getBytes("ASCII");
} catch (UnsupportedEncodingException e) {
return "";
}
for (byte b : bytes) {
for (int i = 0; i < 8; i++) {
boolean bit = ((b >> (7 - i) & 1) == 1);
boolean c15 = ((crc >> 15 & 1) == 1);
crc <<= 1;
if (c15 ^ bit) {
crc ^= polynomial;
}
}
}
crc &= 0xffff;
return (String.format("%04X", crc).toUpperCase());
}
}