All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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