
com.github.mob41.blapi.BLDevice Maven / Gradle / Ivy
/*******************************************************************************
* MIT License
*
* Copyright (c) 2016, 2017 Anthony Law
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Contributors:
* - Anthony Law (mob41) - Initial API Implementation
* - bwssytems
* - Christian Fischer (computerlyrik)
*******************************************************************************/
package com.github.mob41.blapi;
import java.io.Closeable;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.xml.bind.DatatypeConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.mob41.blapi.mac.Mac;
import com.github.mob41.blapi.pkt.CmdPacket;
import com.github.mob41.blapi.pkt.CmdPayload;
import com.github.mob41.blapi.pkt.Packet;
import com.github.mob41.blapi.pkt.auth.AES;
import com.github.mob41.blapi.pkt.auth.AuthCmdPayload;
import com.github.mob41.blapi.pkt.dis.DiscoveryPacket;
/**
* This is the base class of all Broadlink devices (e.g. SP1, RMPro)
*
* @author Anthony
*
*/
public abstract class BLDevice implements Closeable {
/**
* The specific logger for this class
*/
protected static final Logger log = LoggerFactory.getLogger(BLDevice.class);
/**
* Initial key for encryption
*/
public static final byte[] INITIAL_KEY = { 0x09, 0x76, 0x28, 0x34, 0x3f, (byte) 0xe9, (byte) 0x9e, 0x23, 0x76, 0x5c,
0x15, 0x13, (byte) 0xac, (byte) 0xcf, (byte) 0x8b, 0x02 }; // 16-byte
/**
* Initial iv for encryption
*/
public static final byte[] INITIAL_IV = { 0x56, 0x2e, 0x17, (byte) 0x99, 0x6d, 0x09, 0x3d, 0x28, (byte) 0xdd,
(byte) 0xb3, (byte) 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58 }; // 16-short
public static final int DEFAULT_BYTES_SIZE = 0x38; // 56-bytes
// Devices type HEX
public static final short DEV_SP1 = 0x0;
public static final short DEV_SP2 = 0x2711;
public static final short DEV_SP2_HONEYWELL_ALT1 = 0x2719;
public static final short DEV_SP2_HONEYWELL_ALT2 = 0x7919;
public static final short DEV_SP2_HONEYWELL_ALT3 = 0x271a;
public static final short DEV_SP2_HONEYWELL_ALT4 = 0x791a;
public static final short DEV_SPMINI = 0x2720;
public static final short DEV_SP3 = 0x753e;
public static final short DEV_SPMINI2 = 0x2728;
public static final short DEV_SPMINI_OEM_ALT1 = 0x2733;
public static final short DEV_SPMINI_OEM_ALT2 = 0x273e;
public static final short DEV_SPMINI_PLUS = 0x2736;
public static final short DEV_RM_2 = 0x2712;
public static final short DEV_RM_MINI = 0x2737;
public static final short DEV_RM_PRO_PHICOMM = 0x273d;
public static final short DEV_RM_2_HOME_PLUS = 0x2783;
public static final short DEV_RM_2_2HOME_PLUS_GDT = 0x277c;
public static final short DEV_RM_2_PRO_PLUS = 0x272a;
public static final short DEV_RM_2_PRO_PLUS_2 = 0x2787;
public static final short DEV_RM_2_PRO_PLUS_2_BL = 0x278b;
public static final short DEV_RM_MINI_SHATE = 0x278f;
public static final short DEV_A1 = 0x2714;
public static final short DEV_MP1 = 0x4EB5;
public static final short DEV_HYSEN = 0x4EAD;
public static final short DEV_FLOUREON = 0xffffffad;
//
// Friendly device description
//
// Notice: Developers are not recommended to use device description as device identifiers.
// Instead, developers are advised to use Device Type Hex numbers.
//Unknown
public static final String DESC_UNKNOWN = "Unknown Device";
//RM Series
public static final String DESC_RM_2 = "RM 2";
public static final String DESC_RM_MINI = "RM Mini";
public static final String DESC_RM_PRO_PHICOMM = "RM Pro";
public static final String DESC_RM_2_HOME_PLUS = "RM 2 Home Plus";
public static final String DESC_RM_2_2HOME_PLUS_GDT = "RM 2 Home Plus GDT";
public static final String DESC_RM_2_PRO_PLUS = "RM 2 Pro Plus";
public static final String DESC_RM_2_PRO_PLUS_2 = "RM 2 Pro Plus 2";
public static final String DESC_RM_2_PRO_PLUS_2_BL = "RM 2 Pro Plus 2 BL";
public static final String DESC_RM_MINI_SHATE = "RM Mini SHATE";
//A Series
public static final String DESC_A1 = "Environmental Sensor";
//MP Series
public static final String DESC_MP1 = "Power Strip";
//SP Series
public static final String DESC_SP1 = "Smart Plug V1";
public static final String DESC_SP2 = "Smart Plug V2";
public static final String DESC_SP2_HONEYWELL_ALT1 = "Smart Plug Honeywell Alt 1";
public static final String DESC_SP2_HONEYWELL_ALT2 = "Smart Plug Honeywell Alt 2";
public static final String DESC_SP2_HONEYWELL_ALT3 = "Smart Plug Honeywell Alt 3";
public static final String DESC_SP2_HONEYWELL_ALT4 = "Smart Plug Honeywell Alt 4";
public static final String DESC_SPMINI = "Smart Plug Mini";
public static final String DESC_SP3 = "Smart Plug V3";
public static final String DESC_SPMINI2 = "Smart Plug Mini V2";
public static final String DESC_SPMINI_OEM_ALT1 = "Smart Plug OEM Alt 1";
public static final String DESC_SPMINI_OEM_ALT2 = "Smart Plug OEM Alt 2";
public static final String DESC_SPMINI_PLUS = "Smart Plug Mini Plus";
public static final String DESC_HYSEN = "Hysen Thermostat";
public static final String DESC_FLOUREON = "Floureon Thermostat";
/**
* The destination port for discovery broadcasting (from __init__.py)
*/
public static final int DISCOVERY_DEST_PORT = 80;
/**
* The discovery receive buffer size (from __init__.py)
*/
public static final int DISCOVERY_RECEIVE_BUFFER_SIZE = 0x40; // 64-bytes
/**
* Default discovery timeout (10 seconds)
*/
public static final int DEFAULT_TIMEOUT = 10000; // 10 seconds (10000 ms)
/**
* Packet count that is sent by this instance of BLDevice. This is for
* {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
*/
private int pktCount;
/**
* Encryption key. Initialization value is {@link #INITIAL_KEY INITIAL_KEY}.
* This is for {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
*/
private byte[] key;
/**
* Encryption iv. Initialization value is {@link #INITIAL_IV INITIAL_IV}.
* This is for {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
*/
private byte[] iv;
/**
* Device/Client ID. Initialization value is {0,0,0,0}
. And it
* is changed after the {@link #auth() auth} method, that Broadlink devices
* will provide a id for this client/device. This is for
* {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
*/
private byte[] id;
/**
* Device type received from discovering devices, or those
* BLDevice.DEV_*
constants
*/
private final short deviceType;
/**
* A friendly description of this device
*/
private final String deviceDesc;
/**
* Specific datagram socket for this instance, to reuse address.
*/
private DatagramSocket sock;
/**
* Target device host
*/
private String host;
/**
* Target device MAC, using {@link com.github.mob41.blapi.mac.Mac}
* implementation to handle MAC addresses
*/
private Mac mac;
/**
* AES decryption object
*/
private AES aes = null;
/**
* flag to denote this object alreay authorized.
*/
private boolean alreadyAuthorized;
/**
* Constructs a BLDevice
, with a device type (constants),
* hostname and MAC address
*
* @param deviceType
* Device type constants (BLDevice.DEV_*
)
* @param deviceDesc
* Friendly device description
* @param host
* Hostname of target Broadlink device
* @param mac
* MAC address of target Broadlink device
* @throws IOException
* Problems on constructing a datagram socket
*/
protected BLDevice(short deviceType, String deviceDesc, String host, Mac mac) throws IOException {
key = INITIAL_KEY;
iv = INITIAL_IV;
id = new byte[] { 0, 0, 0, 0 };
pktCount = new Random().nextInt(0xffff);
this.deviceType = deviceType;
this.deviceDesc = deviceDesc;
this.host = host;
this.mac = mac;
sock = new DatagramSocket();
sock.setReuseAddress(true);
sock.setBroadcast(true);
aes = new AES(iv, key);
alreadyAuthorized = false;
}
/**
* Releases the resources of this BLDevice
*/
@Override
public void close() {
sock.close();
}
/**
* Returns the device type of this Broadlink device
*
* @return The device type in short
*/
public short getDeviceType() {
return deviceType;
}
/**
* Returns this Broadlink device's hostname / IP address
*
* @return The hostname / IP address in String
*/
public String getHost() {
return host;
}
/**
* Returns this Broadlink device's MAC address
*
* @return The MAC address in BLApi's Mac
implementation
*/
public Mac getMac() {
return mac;
}
public AES getAes() {
return aes;
}
/**
* Returns a friendly description of this BLDevice
* @return a String
*/
public String getDeviceDescription() {
return deviceDesc;
}
/**
* Compatibility with previous code
* @return Boolean whether this method is success or not
* @throws IOException If I/O goes wrong
*/
public boolean auth() throws IOException {
return auth(false);
}
/**
* Authenticates with the broadlink device, before any other control
* commands
* @param reauth Setting this to true forces to perform re-auth with the device. Defaults not to perform re-auth.
* @return Boolean whether this method is success or not
* @throws IOException
* If I/O goes wrong
*/
public boolean auth(boolean reauth) throws IOException {
log.debug("auth Authentication method starts");
if(alreadyAuthorized && !reauth) {
log.debug("auth Already Authorized.");
return true;
}
AuthCmdPayload sendPayload = new AuthCmdPayload();
log.debug("auth Sending CmdPacket with AuthCmdPayload: cmd=" + Integer.toHexString(sendPayload.getCommand())
+ " len=" + sendPayload.getPayload().getData().length);
log.debug("auth AuthPayload initial bytes to send: {}", DatatypeConverter.printHexBinary(sendPayload.getPayload().getData()));
DatagramPacket recvPack = sendCmdPkt(10000, 2048, sendPayload);
byte[] data = recvPack.getData();
if(data.length <= 0) {
log.error("auth Received 0 bytes on initial request.");
alreadyAuthorized = false;
return false;
}
log.debug("auth recv encrypted data bytes (" + data.length +") after initial req: {}", DatatypeConverter.printHexBinary(data));
byte[] payload = null;
try {
log.debug("auth Decrypting encrypted data");
payload = decryptFromDeviceMessage(data);
log.debug("auth Decrypted. len=" + payload.length);
} catch (Exception e) {
log.error("auth Received datagram decryption error. Aborting method", e);
alreadyAuthorized = false;
return false;
}
log.debug("auth Packet received payload bytes: " + DatatypeConverter.printHexBinary(payload));
key = subbytes(payload, 0x04, 0x14);
log.debug("auth Packet received key bytes: " + DatatypeConverter.printHexBinary(key));
if (key.length % 16 != 0) {
log.error("auth Received key len is not a multiple of 16! Aborting");
alreadyAuthorized = false;
return false;
}
// recreate AES object with new key
aes = new AES(iv, key);
id = subbytes(payload, 0x00, 0x04);
log.debug("auth Packet received id bytes: " + DatatypeConverter.printHexBinary(id) + " with ID len=" + id.length);
log.debug("auth End of authentication method");
alreadyAuthorized = true;
return true;
}
/**
* Sends a command packet from localhost to Broadlink device, with buffer
* size 1024 bytes, 10 seconds timeout
*
* Before any commands to be sent to the device, {@link #auth() auth} must
* be ran first in order to authenticate with the device and gain a device
* ID, encryption key and IV.
*
* @param cmdPayload
* Command data to be sent
* @return {@link DatagramPacket} containing the byte data and sender host
* information.
* @throws IOException
* Problems when sending the packet
*/
public DatagramPacket sendCmdPkt(CmdPayload cmdPayload) throws IOException {
return sendCmdPkt(10000, cmdPayload);
}
/**
* Sends a command packet from localhost to Broadlink device, with default
* buffer size 1024 bytes
*
* Before any commands to be sent to the device, {@link #auth() auth} must
* be ran first in order to authenticate with the device and gain a device
* ID, encryption key and IV.
*
* @param timeout
* Socket read timeout
* @param cmdPayload
* Command data to be sent
* @return {@link DatagramPacket} containing the byte data and sender host
* information.
* @throws IOException
* Problems when sending the packet
*/
public DatagramPacket sendCmdPkt(int timeout, CmdPayload cmdPayload) throws IOException {
return sendCmdPkt(InetAddress.getLocalHost(), 0, timeout, 1024, cmdPayload);
}
/**
* Sends a command packet from localhost to Broadlink device
*
* Before any commands to be sent to the device, {@link #auth() auth} must
* be ran first in order to authenticate with the device and gain a device
* ID, encryption key and IV.
*
* @param timeout
* Socket read timeout
* @param bufSize
* Receive datagram buffer size
* @param cmdPayload
* Command data to be sent
* @return {@link DatagramPacket} containing the byte data and sender host
* information.
* @throws IOException
* Problems when sending the packet
*/
public DatagramPacket sendCmdPkt(int timeout, int bufSize, CmdPayload cmdPayload) throws IOException {
return sendCmdPkt(InetAddress.getLocalHost(), 0, timeout, bufSize, cmdPayload);
}
/**
* Binds to a specific IP address and sends a command packet to Broadlink
* device
*
* Before any commands to be sent to the device, {@link #auth() auth} must
* be ran first in order to authenticate with the device and gain a device
* ID, encryption key and IV.
*
* @param sourceIpAddr
* Bind the socket to this IP address
* @param sourcePort
* Bind the socket to this port
* @param timeout
* Socket read timeout
* @param bufSize
* Receive datagram buffer size
* @param cmdPayload
* Command data to be sent
* @return {@link DatagramPacket} containing the byte data and sender host
* information.
* @throws IOException
* Problems when sending the packet
*/
public DatagramPacket sendCmdPkt(InetAddress sourceIpAddr, int sourcePort, int timeout, int bufSize,
CmdPayload cmdPayload) throws IOException {
CmdPacket cmdPkt = new CmdPacket(mac, pktCount++, id, aes, cmdPayload);
log.debug("sendCmdPkt - Send Command Packet bytes: {}", DatatypeConverter.printHexBinary(cmdPkt.getData()));
return sendPkt(sock, cmdPkt, InetAddress.getByName(host), 80, timeout, bufSize);
}
/**
* Creates a Broadlink device client
*
* @param deviceType
* Device type constant (BLDevice.DEV_*
)
* @param host
* Target Broadlink device hostname
* @param mac
* Target Broadlink device MAC address
* @return A BLDevice client
* @throws IOException
* Problems when constucting a datagram socket
*/
public static BLDevice createInstance(short deviceType, String host, Mac mac) throws IOException {
String desc = BLDevice.getDescOfType(deviceType);
switch (deviceType) {
case DEV_SP1:
return new SP1Device(host, mac);
case DEV_SP2:
case DEV_SP2_HONEYWELL_ALT1:
case DEV_SP2_HONEYWELL_ALT2:
case DEV_SP2_HONEYWELL_ALT3:
case DEV_SP2_HONEYWELL_ALT4:
case DEV_SPMINI:
case DEV_SP3:
case DEV_SPMINI2:
case DEV_SPMINI_OEM_ALT1:
case DEV_SPMINI_OEM_ALT2:
case DEV_SPMINI_PLUS:
return new SP2Device(deviceType, desc, host, mac);
case DEV_RM_2:
case DEV_RM_MINI:
case DEV_RM_PRO_PHICOMM:
case DEV_RM_2_HOME_PLUS:
case DEV_RM_2_2HOME_PLUS_GDT:
case DEV_RM_2_PRO_PLUS:
case DEV_RM_2_PRO_PLUS_2:
case DEV_RM_2_PRO_PLUS_2_BL:
case DEV_RM_MINI_SHATE:
return new RM2Device(deviceType, desc, host, mac);
case DEV_A1:
return new A1Device(host, mac);
case DEV_MP1:
return new MP1Device(host, mac);
case DEV_FLOUREON:
return new FloureonDevice(host, mac);
case DEV_HYSEN:
return new HysenDevice(host, mac);
}
return null;
}
/**
* Discover Broadlink devices in the local network, with
* {@link #DEFAULT_TIMEOUT default timeout}
*
* @return An array of BLDevice
in the network
* @throws IOException
* Problems when discovering
*/
public static BLDevice[] discoverDevices() throws IOException {
return discoverDevices(DEFAULT_TIMEOUT);
}
/**
* Discover Broadlink devices in the local network
*
* @param timeout
* Socket read timeout
* @return An array of BLDevice
in the network
* @throws IOException
* Problems when discovering
*/
public static BLDevice[] discoverDevices(int timeout) throws IOException {
return discoverDevices(InetAddress.getLocalHost(), 0, timeout);
}
/**
* Discover Broadlink devices in the network, binded with a specific IP
* address
*
* @param sourceIpAddr
* The IP address to be binded
* @param sourcePort
* The port to be binded
* @param timeout
* Socket read timeout
* @return An array of BLDevice
in the network
* @throws IOException
* Problems when discovering
*/
public static BLDevice[] discoverDevices(InetAddress sourceIpAddr, int sourcePort, int timeout) throws IOException {
boolean debug = log.isDebugEnabled();
if (debug)
log.debug("Discovering devices");
List devices = new ArrayList(50);
if (debug)
log.debug("Constructing DiscoveryPacket");
DiscoveryPacket dpkt = new DiscoveryPacket(sourceIpAddr, sourcePort);
DatagramSocket sock = new DatagramSocket(sourcePort, sourceIpAddr);
sock.setBroadcast(true);
sock.setReuseAddress(true);
byte[] sendBytes = dpkt.getData();
DatagramPacket sendpack = new DatagramPacket(sendBytes, sendBytes.length,
InetAddress.getByName("255.255.255.255"), DISCOVERY_DEST_PORT);
if (debug)
log.debug("Sending broadcast");
sock.send(sendpack);
byte[] receBytes = new byte[DISCOVERY_RECEIVE_BUFFER_SIZE];
DatagramPacket recePacket = new DatagramPacket(receBytes, 0, receBytes.length);
if (timeout == 0) {
if (debug)
log.debug("No timeout was set. Blocking thread until received");
log.debug("Waiting for datagrams");
sock.receive(recePacket);
if (debug)
log.debug("Received. Closing socket");
sock.close();
String host = recePacket.getAddress().getHostAddress();
Mac mac = new Mac(subbytes(receBytes, 0x3a, 0x40));
short deviceType = (short) (receBytes[0x34] | receBytes[0x35] << 8);
if (debug)
log.debug("Info: host=" + host + " mac=" + mac.getMacString() + " deviceType=0x"
+ Integer.toHexString(deviceType));
log.debug("Creating BLDevice instance");
BLDevice inst = createInstance(deviceType, host, mac);
if (inst != null) {
if (debug)
log.debug("Adding to found devices list");
devices.add(inst);
} else if (debug) {
log.debug("Cannot create instance, returned null, not adding to found devices list");
}
} else {
if (debug)
log.debug("A timeout of " + timeout + " ms was set. Running loop");
long startTime = System.currentTimeMillis();
long elapsed;
while ((elapsed = System.currentTimeMillis() - startTime) < timeout) {
if (debug)
log.debug("Elapsed: " + elapsed + " ms");
log.debug("Socket timeout: timeout-elapsed=" + (timeout - elapsed));
sock.setSoTimeout((int) (timeout - elapsed));
try {
if (debug)
log.debug("Waiting for datagrams");
sock.receive(recePacket);
} catch (SocketTimeoutException e) {
if (debug)
log.debug("Socket timed out for " + (timeout - elapsed) + " ms", e);
break;
}
if (debug)
log.debug("Received datagram");
String host = recePacket.getAddress().getHostAddress();
Mac mac = new Mac(reverseBytes(subbytes(receBytes, 0x3a, 0x40)));
short deviceType = (short) (receBytes[0x34] | receBytes[0x35] << 8);
if (debug)
log.debug("Info: host=" + host + " mac=" + mac.getMacString() + " deviceType=0x"
+ Integer.toHexString(deviceType));
log.debug("Creating BLDevice instance");
BLDevice inst = createInstance(deviceType, host, mac);
if (inst != null) {
if (debug)
log.debug("Adding to found devices list");
devices.add(inst);
} else if (debug) {
log.debug("Cannot create instance, returned null, not adding to found devices list");
}
}
}
if (debug)
log.debug("Converting list to array: " + devices.size());
BLDevice[] out = new BLDevice[devices.size()];
for (int i = 0; i < out.length; i++) {
out[i] = devices.get(i);
}
if (debug)
log.debug("End of device discovery: " + out.length);
sock.close();
return out;
}
public static String getDescOfType(short devType){
switch (devType) {
//
// RM Series
//
case BLDevice.DEV_RM_2:
return DESC_RM_2;
case BLDevice.DEV_RM_MINI:
return DESC_RM_MINI;
case BLDevice.DEV_RM_PRO_PHICOMM:
return DESC_RM_PRO_PHICOMM;
case BLDevice.DEV_RM_2_HOME_PLUS:
return DESC_RM_2_HOME_PLUS;
case BLDevice.DEV_RM_2_2HOME_PLUS_GDT:
return DESC_RM_2_2HOME_PLUS_GDT;
case BLDevice.DEV_RM_2_PRO_PLUS:
return DESC_RM_2_PRO_PLUS;
case BLDevice.DEV_RM_2_PRO_PLUS_2:
return DESC_RM_2_PRO_PLUS_2;
case BLDevice.DEV_RM_2_PRO_PLUS_2_BL:
return DESC_RM_2_PRO_PLUS_2_BL;
case BLDevice.DEV_RM_MINI_SHATE:
return DESC_RM_MINI_SHATE;
//
// SP2 Series
//
case BLDevice.DEV_SP2:
return DESC_SP2;
case BLDevice.DEV_SP2_HONEYWELL_ALT1:
return DESC_SP2_HONEYWELL_ALT1;
case BLDevice.DEV_SP2_HONEYWELL_ALT2:
return DESC_SP2_HONEYWELL_ALT2;
case BLDevice.DEV_SP2_HONEYWELL_ALT3:
return DESC_SP2_HONEYWELL_ALT3;
case BLDevice.DEV_SP2_HONEYWELL_ALT4:
return DESC_SP2_HONEYWELL_ALT4;
case BLDevice.DEV_SP3:
return DESC_SP3;
case BLDevice.DEV_SPMINI:
return DESC_SPMINI;
case BLDevice.DEV_SPMINI2:
return DESC_SPMINI2;
case BLDevice.DEV_SPMINI_OEM_ALT1:
return DESC_SPMINI_OEM_ALT1;
case BLDevice.DEV_SPMINI_OEM_ALT2:
return DESC_SPMINI_OEM_ALT2;
case BLDevice.DEV_SPMINI_PLUS:
return DESC_SPMINI_PLUS;
case BLDevice.DEV_SP1:
return BLDevice.DESC_SP1;
case BLDevice.DEV_MP1:
return BLDevice.DESC_MP1;
case BLDevice.DEV_A1:
return BLDevice.DESC_A1;
case BLDevice.DEV_HYSEN:
return BLDevice.DESC_HYSEN;
case BLDevice.DEV_FLOUREON:
return BLDevice.DESC_FLOUREON;
//
// Unregonized
//
default:
return DESC_UNKNOWN;
}
}
/**
* Misc: Reverse the byte array
*
* @param data
* Original data
* @return Result byte array
*/
public static byte[] reverseBytes(byte[] data) {
byte[] out = new byte[data.length];
for (int i = 0; i < out.length; i++) {
out[i] = data[data.length - 1 - i];
}
return out;
}
/**
* Misc: Pull bytes out from end of array until a non null is detected
*
* @param data
* Original data
* @param offset
* Starting offset
* @return Result byte array
*/
public static byte[] removeNullsFromEnd(byte[] data, int offset) {
int new_length = 0;
for (int i = data.length - 1; i >= offset; i--) {
if (data[i] != 0x00) { // null
new_length = i + 1;
break;
}
}
byte[] out = new byte[new_length];
for (int x = offset; x < new_length; x++) {
out[x - offset] = data[x];
}
return out;
}
/**
* Misc: Pull bytes out from an array until a NULL (0) is detected
*
* @param data
* Original data
* @param offset
* Starting offset
* @return Result byte array
*/
public static byte[] subbytesTillNull(byte[] data, int offset) {
int new_length = 0;
for (int i = offset; i < data.length; i++) {
if (data[i] == 0x00) { // null
new_length = i;
break;
}
}
byte[] out = new byte[new_length];
for (int x = offset; x < new_length; x++) {
out[x - offset] = data[x];
}
return out;
}
/**
* Get Payload without header and padded for decryption.
*
* @param data the encrypted data message from the device and includes the header
* @return Payload bytes without the header and padded to modulo 16
*/
public byte[] getRawPayloadBytesPadded(byte[] data) {
byte[] encData = subbytes(data, BLDevice.DEFAULT_BYTES_SIZE, data.length);
byte[] newBytes = null;
if(encData.length > 0) {
int numpad = 16 - (encData.length % 16);
newBytes = new byte[encData.length+numpad];
for(int i = 0; i < newBytes.length; i++) {
if(i < encData.length)
newBytes[i] = encData[i];
else
newBytes[i] = 0x00;
}
}
return newBytes;
}
protected byte[] decryptFromDeviceMessage(byte[] encData) throws Exception {
byte[] encPL = getRawPayloadBytesPadded(encData);
byte[] pl = aes.decrypt(encPL);
return pl;
}
/**
* Picks bytes from start-set to the end-set in a bytes array
*
* @param data
* The bytes array to be used
* @param start
* The starting position to be picked
* @param end
* The ending position to be picked
* @return The bytes array picked with length (end - start
)
*/
public static byte[] subbytes(byte[] data, int start, int end) {
byte[] out = new byte[end - start];
int outi = 0;
for (int i = start; i < end; i++, outi++) {
out[outi] = data[i];
}
return out;
}
/**
* Sends a compiled packet to a destination host and port, and receives a
* datagram from the source port specified.
*
* @param pkt
* The compiled packet to be sent
* @param sourceIpAddr
* Source IP address to be binded for receiving datagrams
* @param sourcePort
* Source Port to be bineded for receiving datagrams
* @param destIpAddr
* Destination IP address
* @param destPort
* Destination Port
* @param timeout
* Socket timeout. 0 will disable the timeout
* @param bufSize
* Receiving datagram's buffer size
* @return The received datagram
* @throws IOException
* Thrown if socket timed out, cannot bind source IP and source
* port, no permission, etc.
*/
public static DatagramPacket sendPkt(Packet pkt, InetAddress sourceIpAddr, int sourcePort, InetAddress destIpAddr,
int destPort, int timeout, int bufSize) throws IOException {
log.debug("sendPkt - call with create socket for: " + sourceIpAddr.getHostAddress() + " and port " + sourcePort);
DatagramSocket sock = new DatagramSocket(sourcePort, sourceIpAddr);
sock.setBroadcast(true);
sock.setReuseAddress(true);
DatagramPacket recePkt = sendPkt(sock, pkt, destIpAddr, destPort, timeout, bufSize);
sock.close();
return recePkt;
}
/**
* Sends a compiled packet to a destination host and port, and receives a
* datagram from the source port specified.
*
* @param sock
* Uses an external socket
* @param pkt
* The compiled packet to be sent
* @param destIpAddr
* Destination IP address
* @param destPort
* Destination Port
* @param timeout
* Socket timeout. 0 will disable the timeout
* @param bufSize
* Receiving datagram's buffer size
* @return The received datagram
* @throws IOException
* Thrown if socket timed out, cannot bind source IP and source
* port, no permission, etc.
*/
public static DatagramPacket sendPkt(DatagramSocket sock, Packet pkt, InetAddress destIpAddr, int destPort, int timeout, int bufSize) throws IOException {
String boundHost = null;
if(sock.getInetAddress() == null)
boundHost = "0.0.0.0";
else
boundHost = sock.getInetAddress().getHostAddress();
log.debug("sendPkt - call with given sock for " + boundHost + " and port " + sock.getPort());
byte[] data = pkt.getData();
DatagramPacket sendpack = new DatagramPacket(data, data.length, destIpAddr, destPort);
log.debug("snedPkt - data for length: " + data.length + " to: " + sendpack.getAddress().getHostAddress() + " for port: " + sendpack.getPort());
byte[] rece = new byte[bufSize];
DatagramPacket recepack = new DatagramPacket(rece, 0, rece.length);
long startTime = System.currentTimeMillis();
long elapsed;
while ((elapsed = System.currentTimeMillis() - startTime) < timeout) {
try {
sock.send(sendpack);
sock.setSoTimeout(1000);
sock.receive(recepack);
break;
} catch (SocketTimeoutException e) {
if (elapsed > timeout) {
break;
}
continue;
}
}
log.debug("sendPkt - recv data bytes (" + recepack.getData().length +") after initial req: {}", DatatypeConverter.printHexBinary(recepack.getData()));
recepack.setData(removeNullsFromEnd(recepack.getData(), 0));
return recepack;
}
public static byte[] chgLen(byte[] data, int newLen) {
byte[] newBytes = new byte[newLen];
for (int i = 0; i < data.length; i++) {
newBytes[i] = data[i];
}
return newBytes;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy