package com.dreikraft.axbo.data;
import com.dreikraft.axbo.sound.Sound;
import com.dreikraft.axbo.sound.SoundPackage;
import com.dreikraft.axbo.util.ByteUtil;
import com.dreikraft.axbo.util.StringUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.Calendar;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Utility functions to create and execute aXbo serial interface commands. The
* aXbo protocol definition can be found here:
* https://docs.google.com/document/d/1rQkp8XcMFh0PPenZfhWqaGXDfi2gdxutdeXtu-T6OTo/pub
*
* @author jan.illetschko@3kraft.com
*/
public class AxboCommandUtil {
/**
* The logger.
*/
public static final Log log = LogFactory.getLog(AxboCommandUtil.class);
/**
* The default buffer size.
*/
public static final int BUF_SIZE = 1024;
/**
* The size of an aXbo memory frame in bytes.
*/
public static final int MEM_BUFFER_SIZE = 66;
/**
* The size of an aXbo memory page in bytes.
*/
public static final int MEM_PAGE_SIZE = 66 * 4;
private static final int PROTOCOL_PREFIX_LEN = 8;
private static final int PROTOCOL_SUFFIX_LEN = 4;
/**
* Sleeps the current thread.
*
* @param milliSecs sleep time in msec.
*/
public static void sleep(int milliSecs) {
try {
Thread.sleep(milliSecs);
} catch (InterruptedException ex) {
log.warn(ex.getMessage(), ex);
}
}
/**
* Creates the command to update the date on the aXbo.
*
* @return the command bytes.
*/
public static byte[] getDateCmd() {
Calendar cal = Calendar.getInstance();
log.info("setting alarm clock date " + cal);
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xC8, // set date command
(byte) ((cal.get(Calendar.YEAR) - 2000) / 10), // year 1. digit
(byte) ((cal.get(Calendar.YEAR) - 2000) % 10), // year 2. digit
(byte) ((cal.get(Calendar.MONTH) + 1) / 10), // month 1. digit
(byte) ((cal.get(Calendar.MONTH) + 1) % 10), // month 2. digit
(byte) (cal.get(Calendar.DAY_OF_MONTH) / 10), // day 1.digit
(byte) (cal.get(Calendar.DAY_OF_MONTH) % 10), // day 2.digit
(byte) (cal.get(Calendar.HOUR_OF_DAY) / 10), // hour 1.digit
(byte) (cal.get(Calendar.HOUR_OF_DAY) % 10), // hour 2.digit
(byte) (cal.get(Calendar.MINUTE) / 10), // minute 1.digit
(byte) (cal.get(Calendar.MINUTE) % 10), // minute 2.digit
(byte) 0x00, (byte) 0x00, // second
(byte) 0x10, (byte) 0x03, (byte) 0x00, (byte) 0x00 // protocal end + checksum
};
int checksum = ByteUtil.calcChecksum(cmd, 3, 16);
cmd[cmd.length - 2] = (byte) (checksum >> 8 & 0x000000FF);
cmd[cmd.length - 1] = (byte) (checksum & 0x000000FF);
if (log.isDebugEnabled()) {
log.debug("data: " + Arrays.toString(cmd));
}
return escapeDLE(cmd);
}
/**
* Executes a date/time update on aXbo
*
* @param portName the name of the serial interface.
* @throws DataInterfaceException the update failed
*/
public static void runSetClockDate(final String portName) throws
DataInterfaceException {
// sync communication first
syncInterface(portName);
// execute command
getDataInterface().writeData(portName, AxboCommandUtil.getDateCmd(), 1);
}
/**
* Creates the command to retrieve all movement data from aXbo.
*
* @return
*/
public static byte[] getLogDataCmd() {
log.info("retrieving log data");
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xCD, // get log command
(byte) 0x10, (byte) 0x03, (byte) 0x00, (byte) 0xCE // protocal end + checksum
};
return cmd;
}
/**
* Executes the command to retrieve movement data from aXbo
*
* @param portName the name of the serial interface
* @throws DataInterfaceException if movement data retrieval fails.
*/
public static void runLogDataCmd(final String portName) throws
DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName,
AxboCommandUtil.getLogDataCmd(), 1);
}
/**
* Creates a command to clear the movement data memory on aXbo.
*
* @return the command bytes
*/
public static byte[] getClearLogDataCmd() {
log.info("clear log data");
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xCE, // clear log command
(byte) 0x10, (byte) 0x03, (byte) 0x00, (byte) 0xCF // protocal end + checksum
};
return cmd;
}
/**
* Executes the command to clear the movement data on aXbo.
*
* @param portName the name of the serial interface.
* @throws DataInterfaceException the command failed.
*/
public static void runClearClockData(final String portName) throws
DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName,
AxboCommandUtil.getClearLogDataCmd(), 1);
}
/**
* Creates a command to retrieve the version info from aXbo.
*
* @return the command bytes
*/
public static byte[] getStatusCmd() {
log.info("reading status");
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0x36, // get status command
(byte) 0x10, (byte) 0x03, (byte) 0x00, (byte) 0x37 // protocal end + checksum
};
return cmd;
}
/**
* Executes the status command.
*
* @param portName the serial interface name.
* @throws DataInterfaceException status retrieval failed.
*/
public static void runReadStatus(final String portName)
throws DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName,
AxboCommandUtil.getStatusCmd(), 1);
}
/**
* Creates a dummy command required for syncing the communication with aXbo.
*
* @param toggleBit toogle bit enabled/disabled
* @return the command bytes
*/
public static byte[] getDummyCmd(boolean toggleBit) {
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, // protocol start
(toggleBit ? (byte) 0x81 : (byte) 0x01), // toggle bit
(byte) 0x39, // dummy command
(byte) 0x10, (byte) 0x03, (byte) 0x00,
(byte) (0x39 + (toggleBit ? 0x81 : 0x01)) // protocal end + checksum
};
return cmd;
}
/**
* Creates the aXbo check command.
*
* @return the command bytes.
*/
public static byte[] getCheckCmd() {
log.info("reading status");
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xCB, // get check command
(byte) 0x10, (byte) 0x03, (byte) 0x00, (byte) 0xCC // protocal end + checksum
};
return cmd;
}
/**
* Executes the check command.
*
* @param portName the serial port name.
* @throws DataInterfaceException chack command failed.
*/
public static void runCheckCmd(final String portName)
throws DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName,
AxboCommandUtil.getCheckCmd(), 1);
}
/**
* Create a aXbo test command.
*
* @param commandId the command id. Possible values are:
* <ul>
* <li>Test Mode: 0x00
* <li>Start Alarm: 0x01
* <li>RTC Kalibration: 0x02
* <li>Clear Active: 0x04
* <li>Software Reset: 0x08
* </ul>
* @return the test command bytes
*/
public static byte[] getTestCmd(final byte commandId) {
log.info("running test command");
byte checksum = ((byte) 0xCB);
checksum += commandId;
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xC0, commandId, (byte) 0x0A, // get test command
(byte) 0x10, (byte) 0x03, (byte) 0x00, checksum // protocal end + checksum
};
return cmd;
}
/**
* Executes the test command
*
* @param portName the serial port name.
* @param commandId the command id. Possible values are:
* <ul>
* <li>Test Mode: 0x00
* <li>Start Alarm: 0x01
* <li>RTC Kalibration: 0x02
* <li>Clear Active: 0x04
* <li>Software Reset: 0x08
* </ul>
* @throws DataInterfaceException test command failed.
*/
public static void runTestCmd(final String portName, final byte commandId)
throws DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName, AxboCommandUtil.getTestCmd(
commandId), 1);
}
/**
* Creates anAxbo command to set the serial number of the aXbo.
*
* @param serialNumber the serial number (8 digits, [0-9]{8})
* @return the command
*/
public static byte[] getSetSerialNumberCmd(String serialNumber) {
log.info("setting serial number: " + serialNumber);
if (serialNumber.length() != 8) {
throw new IllegalArgumentException("invalid length: " + serialNumber.
length());
}
char[] digits = new char[8];
int i = 0;
for (char digit : serialNumber.toCharArray()) {
digits[i] = (char) (digit < 48 ? digit + 48 : digit);
//digits[i] = (char)(digit - 48);
i++;
}
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xC5, // get set serial number command
(byte) digits[0],
(byte) digits[1],
(byte) digits[2],
(byte) digits[3],
(byte) digits[PROTOCOL_SUFFIX_LEN],
(byte) digits[5],
(byte) digits[6],
(byte) digits[7],
(byte) 0x10,
(byte) 0x03,
(byte) 0x00,
(byte) 0x00 // protocal end + checksum
// protocal end + checksum
};
int checksum = ByteUtil.calcChecksum(cmd, 3, 13);
cmd[cmd.length - 2] = ByteUtil.highByte(checksum);
cmd[cmd.length - 1] = ByteUtil.lowByte(checksum);
return cmd;
}
/**
* Executes the setting of the serial number.
*
* @param portName the serial port name.
* @param serialNumber the serial number (8 digits, [0-9]{8})
* @throws DataInterfaceException setting of the serial number failed.
*/
public static void runSetSerialNumberCmd(final String portName,
final String serialNumber)
throws DataInterfaceException {
// sync communication first
syncInterface(portName);
getDataInterface().writeData(portName,
AxboCommandUtil.getSetSerialNumberCmd(serialNumber), 1);
}
/**
* Creates the command to clear the serial number.
*
* @return the command.
*/
public static byte[] getClearSerialNumberCmd() {
log.info("clearing serial number");
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xC5, // get set serial number command
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0xFF,
(byte) 0x10,
(byte) 0x03,
(byte) 0x00,
(byte) 0x00 // protocol end + checksum
};
int checksum = ByteUtil.calcChecksum(cmd, 3, 13);
cmd[cmd.length - 2] = ByteUtil.highByte(checksum);
cmd[cmd.length - 1] = ByteUtil.lowByte(checksum);
return cmd;
}
/**
* Clears the serial number of aXbo.
*
* @param portName the serial port name.
* @throws DataInterfaceException clearing failed.
*/
public static void runClearSerialNumberCmd(final String portName)
throws DataInterfaceException {
// sync communication first
syncInterface(portName);
// clear serial number
getDataInterface().writeData(portName, AxboCommandUtil
.getClearSerialNumberCmd(), 1);
}
/**
* Synchronizes the data interface. The data interface to aXbo must be in
* sync, before successful issuing of commands is possible.
*
* @param portName the name of the serial interface.
*/
public static void syncInterface(final String portName) {
// send dummy command with toggle bit off
try {
getDataInterface().writeData(portName, AxboCommandUtil.getDummyCmd(false),
1);
} catch (DataInterfaceException ex) {
if (log.isDebugEnabled())
log.debug(ex.getMessage(), ex);
}
// send dummy command with toogle bit on
try {
getDataInterface().writeData(portName, AxboCommandUtil.getDummyCmd(true),
1);
} catch (DataInterfaceException ex) {
if (log.isDebugEnabled())
log.debug(ex.getMessage(), ex);
}
}
/**
* Clears the data header memory segment in aXbo flash memory.
*
* @param portName the name of the serial interface.
* @throws DataInterfaceException the clearing failed.
*/
public static void clearHeader(final String portName)
throws DataInterfaceException {
int bufferPos = 0;
boolean toggleBit = false;
for (int i = 0; i < 16; i++) {
final byte[] protocol = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) (toggleBit ? 0x81 : 0x01), // protocol start
(byte) 0xBC, // write buffer
(byte) 0x00, // buffer pos high byte
(byte) bufferPos,
(byte) 0x10, // data len with stuffing
(byte) 0x22, // header start
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00,
(byte) 0x00, // empty
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // name placeholder
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // name placeholder
(byte) 0x10, // protocol end
(byte) 0x03,
(byte) 0x00, // checksum
(byte) 0x00 // checksum
};
// checksum
int checksum = ByteUtil.calcChecksum(protocol, 3, 24); // substract stuffing
protocol[protocol.length - 2] = ByteUtil.highByte(checksum);
protocol[protocol.length - 1] = ByteUtil.lowByte(checksum);
// write data to buffer
getDataInterface().writeData(portName, escapeDLE(protocol), 3);
toggleBit = !toggleBit;
bufferPos += 16;
}
// synchronize toggle bit
getDataInterface().writeData(portName, getDummyCmd(true), 3);
// write buffer to first page
writeBufferToPage(portName, 0);
}
/**
* Writes a new sound data header to aXbo flash memory. It contains the
* pointers to the memory area of each sound data. The header is in the first
* page of the flash memory.
*
* @param portName the name of the serial interface
* @param soundPackage the currently uploaded sound package
* @throws DataInterfaceException if writing of the header fails
*/
public static void writeHeader(final String portName,
final SoundPackage soundPackage)
throws DataInterfaceException {
int bufferPos = 16;
boolean toggleBit = false;
for (final Sound sound : soundPackage.getSounds()) {
int endPage = sound.getStartPage() + sound.getPageCount() - 1;
final byte[] protocol = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) (toggleBit ? 0x81 : 0x01), // protocol start
(byte) 0xBC, // write buffer
(byte) 0x00, // buffer pos high byte
(byte) bufferPos,
(byte) 0x10, // data len with stuffing
(byte) 0x22, // header start
ByteUtil.lowByte(sound.getStartPage()),
ByteUtil.highByte(sound.getStartPage()),
ByteUtil.lowByte(endPage),
ByteUtil.highByte(endPage),
(byte) 0x00, // empty
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // name placeholder
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, // name placeholder
(byte) 0x10, // protocol end
(byte) 0x03,
(byte) 0x00, // checksum
(byte) 0x00 // checksum
};
// write name
char[] nameChars = StringUtil.rpad(sound.getName(), ' ', 10).toCharArray();
for (int i = 0; i < nameChars.length; i++) {
protocol[i + 14] = (byte) nameChars[i];
}
// checksum
int checksum = ByteUtil.calcChecksum(protocol, 3, 24); // substract stuffing
protocol[protocol.length - 2] = ByteUtil.highByte(checksum);
protocol[protocol.length - 1] = ByteUtil.lowByte(checksum);
// write data to buffer
getDataInterface().writeData(portName, escapeDLE(protocol), 3);
toggleBit = !toggleBit;
bufferPos += 16;
}
// synchronize toggle bit
getDataInterface().writeData(portName, getDummyCmd(true), 3);
// write buffer to first page
writeBufferToPage(portName, 0);
}
/**
* Writes sound data to buffer memory.
*
* @param portName the serial port name.
* @param soundData the sound date bytes.
* @param page the memory page number.
* @throws DataInterfaceException writing of data failed.
*/
public static void writePage(final String portName, final byte[] soundData,
final int page) throws DataInterfaceException {
//final byte highByte = bufferToggle ? (byte)0x00 : (byte)0x80;
final byte highByte = (byte) 0x00;
boolean protocolToggle = true;
final int pageSize = MEM_PAGE_SIZE * AxboDataParser.INSTANCE.getMemSize();
final int frameSize = MEM_BUFFER_SIZE;
// write frames to page buffer
for (int frame = 0; frame < pageSize / frameSize; frame++) {
final int framePos = frame * frameSize;
// protocol len = prefix len + frame len + suffix len
byte[] protocol = new byte[PROTOCOL_PREFIX_LEN + frameSize
+ PROTOCOL_SUFFIX_LEN];
// prefix
int pos = 0;
protocol[pos] = (byte) 0x00;
pos++;
protocol[pos] = (byte) 0x10;
pos++;
protocol[pos] = (byte) 0x02;
pos++;
protocol[pos] = protocolToggle ? (byte) 0x01 : (byte) 0x81;
pos++;
protocol[pos] = (byte) 0xBC;
pos++;
protocol[pos] = ByteUtil.highByte(framePos);
pos++;
protocol[pos] = ByteUtil.lowByte(framePos);
pos++;
protocol[pos] = (byte) frameSize;
pos++;
// copy frame data to protocol
System.arraycopy(soundData, (page * pageSize) + framePos, protocol, pos,
frameSize);
// suffix
pos += frameSize;
protocol[pos] = (byte) 0x10;
pos++;
protocol[pos] = (byte) 0x03;
// checksum
int checksum = ByteUtil.calcChecksum(protocol, 3, PROTOCOL_PREFIX_LEN
+ frameSize);
pos++;
protocol[pos] = ByteUtil.highByte(checksum);
pos++;
protocol[pos] = ByteUtil.lowByte(checksum);
// write frame to buffer
getDataInterface().writeData(portName, escapeDLE(protocol), 3);
// toggle protocol bit
protocolToggle = !protocolToggle;
}
}
/**
* Writing the buffer memory to flash memory page.
*
* @param portName the serial port name.
* @param page the number of the memory page.
* @throws DataInterfaceException writing failed
*/
public static void writeBufferToPage(String portName, int page)
throws DataInterfaceException {
byte pageHighByte = ByteUtil.highByte(page);
byte pageLowByte = ByteUtil.lowByte(page);
byte[] cmd = {
(byte) 0x00, (byte) 0x10, (byte) 0x02, (byte) 0x01, // protocol start
(byte) 0xBD, // get set serial number command
pageHighByte, pageLowByte, // page address
(byte) 0x10, // protocol end
(byte) 0x03,
(byte) 0x00, // checksum
(byte) 0x00 // checksum
};
final int checksum = ByteUtil.calcChecksum(cmd, 3, 7);
cmd[cmd.length - 2] = ByteUtil.highByte(checksum);
cmd[cmd.length - 1] = ByteUtil.lowByte(checksum);
getDataInterface().writeData(portName, escapeDLE(cmd), 3);
// synchronize toggle bit
getDataInterface().writeData(portName, getDummyCmd(true), 3);
}
/**
* Escapes data link escape characters in the byte array.
*
* @param data the data array
* @return the data array with escaped DLEs
*/
public static byte[] escapeDLE(byte[] data) {
ByteArrayInputStream in = new ByteArrayInputStream(data);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@SuppressWarnings("UnusedAssignment")
int b = 0;
int pos = 0;
while ((b = in.read()) != -1) {
if (b == 0x10 && pos > PROTOCOL_SUFFIX_LEN && pos < data.length
- PROTOCOL_SUFFIX_LEN) {
out.write(b);
}
out.write(b);
pos++;
}
return out.toByteArray();
}
/**
* Retrieve the data interface.
*
* @return the data interface of the current device type.
*/
private static DataInterface getDataInterface() {
return DeviceContext.getDeviceType().getDataInterface();
}
}