/* ==================================================================
* SmaSunnyNetFrame.java - Oct 31, 2013 4:34:33 PM
*
* Copyright 2007-2013 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.node.hw.sma.protocol;
/**
* A communication frame for the {@link SmaPacket} packet, using the SunnyNet
* protocol.
*
* <p>
* A SunnyNet frame is composed of a fixed-length header, a {@link SmaPacket},
* and a fixed-length footer, in the following form:
* </p>
*
* <pre>
* # 2 optional sync bytes
* # 1 telegram start byte (0x68)
* # 1 user length byte
* # 1 user length byte repeated
* # 1 telegram start byte (0x68)
* ### the checksum data block starts here
* # SmaPacket; 7 or more bytes
* ### the checksum data block ends here
* # 2 checksum bytes
* # 1 end character byte (0x16)
* </pre>
*
* @author matt
* @version 1.0
*/
public class SmaSunnyNetFrame implements SmaDataFrame {
/** A "wakeup" packet byte. */
public static final byte WAKEUP = (byte) 0xAA;
/** A "telegram" packet byte. */
public static final byte TELEGRAM = (byte) 0x68;
/** An "end" packet byte. */
public static final byte END = (byte) 0x16;
private SmaPacket packet;
private byte[] frame;
private int computedCRC;
/**
* Construct a SmaSunnyNetFrame from a SmaPacket.
*
* @param packet
* the packet
*/
public SmaSunnyNetFrame(SmaPacket packet) {
super();
this.packet = packet;
encodeFrame(packet.getPacket());
}
/**
* Decode a SmaSunnyNetFrame from a byte array.
*
* <p>
* After construction, the {@link #getFrame()} will return the raw bytes
* used to decode this frame object. You can thus tell by the length of that
* array where the next frame should begin if more data bytes are available
* in the {@code data} array.
* </p>
*
* @param data
* the data to decode
* @param offset
* the offset within the data to start decoding at
*/
public SmaSunnyNetFrame(byte[] data, int offset) {
super();
decodeFrame(data, offset);
}
@Override
public byte[] getFrame() {
return frame;
}
@Override
public boolean isValid() {
int crc = getCRC();
return (crc > 0 && computedCRC == crc && packet != null && packet.getCommand() != null && packet
.getCommand() != SmaCommand.Unknown);
}
@Override
public SmaPacket getPacket() {
return packet;
}
/**
* Get the CRC as encoded in the frame data.
*
* <p>
* The CRC is derived from the 3rd and 2nd to last bytes in the frame.
* </p>
*
* @return the CRC
*/
public int getCRC() {
if ( frame == null || frame.length < 9 ) {
return -1;
}
return ((0xFF & frame[frame.length - 3]) | ((0xFF & frame[frame.length - 2]) << 8));
}
/**
* Get the CRC as computed from the raw packet data.
*
* <p>
* When decoding a frame from bytes, this value can be compared to
* {@link #getCRC()} to tell if the packet is valid or not.
* </p>
*
* @return the computed CRC
*/
public int getComputedCRC() {
return computedCRC;
}
private void encodeFrame(byte[] packet) {
byte[] req = new byte[6 + packet.length + 3];
req[0] = WAKEUP; // wakeup
req[1] = WAKEUP;
req[2] = TELEGRAM; // telegram
req[3] = (byte) packet.length; // length of data, twice
req[4] = req[3];
req[5] = TELEGRAM; // telegram
System.arraycopy(packet, 0, req, 6, packet.length);
computedCRC = 0;
for ( int i = 6; i < packet.length; i++ ) {
computedCRC += (0xFF & req[i]);
}
req[req.length - 3] = (byte) (computedCRC & 0xFF); // computedCrc low byte
req[req.length - 2] = (byte) ((computedCRC >> 8) & 0xFF); // computedCrc high byte
req[req.length - 1] = END;
this.frame = req;
}
private void decodeFrame(byte[] data, int offset) {
if ( data == null || data.length + offset < 7 ) {
return;
}
int idx = offset;
// skip (optional) WAKEUP bytes
while ( data[idx] == WAKEUP ) {
idx++;
}
// next is TELEGRAM, user data length, user data length, TELEGRAM
idx++;
int userDataLength = 0xFF & data[idx];
idx += 3;
byte[] packetData = new byte[7 + userDataLength];
if ( idx + packetData.length + 1 > data.length ) {
// not enough data for packet
return;
}
System.arraycopy(data, idx, packetData, 0, packetData.length);
// CRC data starts now
computedCRC = 0;
for ( int i = 0; i < packetData.length; i++, idx++ ) {
int b = 0xFF & data[idx];
computedCRC += b;
}
this.packet = new SmaPacket(packetData);
// skip the 2 CRC bytes and the final 0x16 byte
idx += 3;
byte[] req = new byte[idx];
System.arraycopy(data, offset, req, 0, req.length);
this.frame = req;
}
}