BLApiException is the super-class of multiple exceptions thrown
33 | * in the library that requires applications to catch.
34 | *
35 | * @author Anthony
36 | *
37 | */
38 | public class BLApiException extends Exception {
39 |
40 | /**
41 | *
42 | */
43 | private static final long serialVersionUID = 6978231778391256347L;
44 |
45 | public BLApiException(String arg0) {
46 | super(arg0);
47 | }
48 |
49 | public BLApiException(Throwable arg0) {
50 | super(arg0);
51 | }
52 |
53 | public BLApiException(String arg0, Throwable arg1) {
54 | super(arg0, arg1);
55 | }
56 |
57 | public BLApiException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
58 | super(arg0, arg1, arg2, arg3);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/cmd/rm2/CheckDataCmdPayload.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.cmd.rm2;
30 |
31 | import com.github.mob41.blapi.pkt.CmdPayload;
32 | import com.github.mob41.blapi.pkt.Payload;
33 |
34 | public final class CheckDataCmdPayload implements CmdPayload {
35 |
36 | private final Payload payload;
37 |
38 | private final byte[] payloadBytes;
39 |
40 | public CheckDataCmdPayload() {
41 | payloadBytes = new byte[16];
42 | payloadBytes[0] = 0x04;
43 |
44 | payload = new Payload() {
45 |
46 | @Override
47 | public byte[] getData() {
48 | return payloadBytes;
49 | }
50 |
51 | };
52 | }
53 |
54 | @Override
55 | public byte getCommand() {
56 | return 0x6a;
57 | }
58 |
59 | @Override
60 | public Payload getPayload() {
61 | return payload;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/cmd/rm2/EnterLearnCmdPayload.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.cmd.rm2;
30 |
31 | import com.github.mob41.blapi.pkt.CmdPayload;
32 | import com.github.mob41.blapi.pkt.Payload;
33 |
34 | public final class EnterLearnCmdPayload implements CmdPayload {
35 |
36 | private final Payload payload;
37 |
38 | private final byte[] payloadBytes;
39 |
40 | public EnterLearnCmdPayload() {
41 | payloadBytes = new byte[16];
42 | payloadBytes[0] = 0x03;
43 |
44 | payload = new Payload() {
45 |
46 | @Override
47 | public byte[] getData() {
48 | return payloadBytes;
49 | }
50 |
51 | };
52 | }
53 |
54 | @Override
55 | public byte getCommand() {
56 | return 0x6a;
57 | }
58 |
59 | @Override
60 | public Payload getPayload() {
61 | return payload;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/ex/BLApiRuntimeException.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.ex;
30 |
31 | /**
32 | * BLApiRuntimeException is the super-class of multiple exceptions
33 | * thrown in the runtime of the library.
34 | *
35 | * @author Anthony
36 | *
37 | */
38 | public class BLApiRuntimeException extends RuntimeException {
39 |
40 | /**
41 | *
42 | */
43 | private static final long serialVersionUID = 6978231778391256347L;
44 |
45 | public BLApiRuntimeException(String arg0) {
46 | super(arg0);
47 | }
48 |
49 | public BLApiRuntimeException(Throwable arg0) {
50 | super(arg0);
51 | }
52 |
53 | public BLApiRuntimeException(String arg0, Throwable arg1) {
54 | super(arg0, arg1);
55 | }
56 |
57 | public BLApiRuntimeException(String arg0, Throwable arg1, boolean arg2, boolean arg3) {
58 | super(arg0, arg1, arg2, arg3);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/auth/AES.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.auth;
30 |
31 | import javax.crypto.Cipher;
32 | import javax.crypto.spec.IvParameterSpec;
33 | import javax.crypto.spec.SecretKeySpec;
34 |
35 | public class AES {
36 |
37 | private static final String CIPHER_ALGO = "AES/CBC/NoPadding";
38 |
39 | private static final String KEY_ALGO = "AES";
40 |
41 | private final byte[] key;
42 |
43 | private final byte[] iv;
44 |
45 | public AES(byte[] iv, byte[] key) {
46 | this.key = key;
47 | this.iv = iv;
48 | }
49 |
50 | public byte[] encrypt(byte[] data) throws Exception {
51 | Cipher c = Cipher.getInstance(CIPHER_ALGO);
52 | c.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, KEY_ALGO), new IvParameterSpec(iv));
53 | return c.doFinal(data);
54 | }
55 |
56 | public byte[] decrypt(byte[] data) throws Exception {
57 | Cipher c = Cipher.getInstance(CIPHER_ALGO);
58 | c.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, KEY_ALGO), new IvParameterSpec(iv));
59 | return c.doFinal(data);
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/auth/AuthPayload.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.auth;
30 |
31 | import com.github.mob41.blapi.pkt.Payload;
32 |
33 | public class AuthPayload implements Payload {
34 |
35 | private final byte[] data;
36 |
37 | public AuthPayload() {
38 | data = new byte[0x50];
39 |
40 | data[0x04] = 0x31;
41 | data[0x05] = 0x31;
42 | data[0x06] = 0x31;
43 | data[0x07] = 0x31;
44 | data[0x08] = 0x31;
45 | data[0x09] = 0x31;
46 | data[0x0a] = 0x31;
47 | data[0x0b] = 0x31;
48 | data[0x0c] = 0x31;
49 | data[0x0d] = 0x31;
50 | data[0x0e] = 0x31;
51 | data[0x0f] = 0x31;
52 | data[0x10] = 0x31;
53 | data[0x11] = 0x31;
54 | data[0x12] = 0x31;
55 | data[0x1e] = 0x01;
56 | data[0x2d] = 0x01;
57 | data[0x30] = (byte) 'T';
58 | data[0x31] = (byte) 'e';
59 | data[0x32] = (byte) 's';
60 | data[0x33] = (byte) 't';
61 | data[0x34] = (byte) ' ';
62 | data[0x35] = (byte) ' ';
63 | data[0x36] = (byte) '1';
64 | }
65 |
66 | @Override
67 | public byte[] getData() {
68 | return data;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/cmd/rm2/SendDataCmdPayload.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.cmd.rm2;
30 |
31 | import com.github.mob41.blapi.pkt.CmdPayload;
32 | import com.github.mob41.blapi.pkt.Payload;
33 |
34 | public final class SendDataCmdPayload implements CmdPayload {
35 |
36 | private final Payload payload;
37 |
38 | private final byte[] payloadBytes;
39 |
40 | private final byte[] dataBytes;
41 |
42 | public SendDataCmdPayload(byte[] irRfCodeData) {
43 | this.dataBytes = irRfCodeData;
44 |
45 | payloadBytes = new byte[4 + dataBytes.length];
46 | payloadBytes[0] = 0x02;
47 |
48 | for (int i = 4; i < dataBytes.length; i++) {
49 | payloadBytes[i] = dataBytes[i - 4];
50 | }
51 |
52 | payload = new Payload() {
53 |
54 | @Override
55 | public byte[] getData() {
56 | return payloadBytes;
57 | }
58 |
59 | };
60 | }
61 |
62 | /**
63 | * Returns the IR/RF code data to be sent
64 | *
65 | * @return IR/RF code data
66 | */
67 | public byte[] getData() {
68 | return dataBytes;
69 | }
70 |
71 | @Override
72 | public byte getCommand() {
73 | return 0x6a;
74 | }
75 |
76 | @Override
77 | public Payload getPayload() {
78 | return payload;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/dev/hysen/AdvancedStatusInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.mob41.blapi.dev.hysen;
2 |
3 | import java.util.Arrays;
4 |
5 | /**
6 | * Advanced status info retrieved from a hysen type thermostat
7 | *
8 | * Adapted from https://github.com/mjg59/python-broadlink
9 | *
10 | * @author alpapad
11 | *
12 | */
13 | public class AdvancedStatusInfo extends BaseStatusInfo {
14 |
15 | private final short hour;
16 | private final short min;
17 | private final short sec;
18 | private final short dayofweek;
19 |
20 | // there are 8 periods available for configuration:
21 | // 6 for weekdays and 2 for weekends.
22 | // Loop mode controls which periods get to be applied during Sat and Sun.
23 | private final Period[] periods = new Period[8];
24 |
25 | private final Period[] weekday = new Period[6];
26 | private final Period[] weekend = new Period[2];
27 |
28 | protected AdvancedStatusInfo(byte[] payload) {
29 | super(payload);
30 |
31 | this.hour = payload[19];
32 | this.min = payload[20];
33 | this.sec = payload[21];
34 | this.dayofweek = payload[22];
35 | if (payload.length>=46) {
36 | for (int i = 0; i < 6; i++) {
37 | this.periods[i] = new Period(i, payload);
38 | this.weekday[i] = this.periods[i];
39 |
40 | }
41 |
42 | for (int i = 6; i <= 7; i++) {
43 | this.periods[i] = new Period(i, payload);
44 | this.weekend[i - 6] = this.periods[i];
45 | }
46 | }
47 | }
48 |
49 | public short getHour() {
50 | return hour;
51 | }
52 |
53 | public short getMin() {
54 | return min;
55 | }
56 |
57 | public short getSec() {
58 | return sec;
59 | }
60 |
61 | public short getDayofweek() {
62 | return dayofweek;
63 | }
64 |
65 | public Period[] getWeekday() {
66 | return weekday;
67 | }
68 |
69 | public Period[] getWeekend() {
70 | return weekend;
71 | }
72 |
73 | public Period[] getPeriods() {
74 | return periods;
75 | }
76 |
77 | @Override
78 | public String toString() {
79 | return "StatusInfo [remote lock=" + remoteLock + ",\n power=" + power + ",\n active=" + active
80 | + ",\n manual temperature=" + manualTemp + ",\n room temp=" + roomTemp + ",\n thermostat temp="
81 | + thermostatTemp + ",\n auto_mode=" + autoMode + ",\n loop_mode=" + loopMode + ",\n SensorControl="
82 | + sensorControl + ",\n osv=" + osv + ",\n dif=" + dif + ",\n svh=" + svh + ",\n svl=" + svl
83 | + ",\n room temp adj=" + roomTempAdjustment + ",\n anti freeze=" + antiFreezing + ",\n powerOnMemory="
84 | + powerOnMemory + ",\n fac?=" + fac + ",\n external temp=" + externalTemp + ",\n hour=" + hour
85 | + ",\n min=" + min + ",\n sec=" + sec + ",\n dayofweek=" + dayofweek + ",\n\n weekday="
86 | + Arrays.toString(weekday) + ",\n\n weekend=" + Arrays.toString(weekend) + "]";
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/cmd/hysen/BaseHysenCommand.java:
--------------------------------------------------------------------------------
1 | package com.github.mob41.blapi.pkt.cmd.hysen;
2 |
3 | import java.net.DatagramPacket;
4 | import java.util.Arrays;
5 |
6 | import javax.xml.bind.DatatypeConverter;
7 |
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 |
11 | import com.github.mob41.blapi.dev.hysen.BaseHysenDevice;
12 | import com.github.mob41.blapi.pkt.CmdPayload;
13 | import com.github.mob41.blapi.pkt.Crc16;
14 | import com.github.mob41.blapi.pkt.Payload;
15 |
16 | /**
17 | * Base hysen command Payload. Handles crc calculation
18 | *
19 | *
20 | * Adapted from https://github.com/mjg59/python-broadlink
21 | *
22 | * @author alpapad
23 | *
24 | */
25 | public abstract class BaseHysenCommand implements CmdPayload {
26 |
27 | protected static final Logger log = LoggerFactory.getLogger(BaseHysenCommand.class);
28 |
29 | public byte[] execute(BaseHysenDevice device) throws Exception {
30 |
31 | DatagramPacket packet = device.sendCmdPkt(this);
32 |
33 | byte[] data = packet.getData();
34 |
35 | log.debug(this.getClass().getSimpleName() + " received encrypted bytes: "
36 | + DatatypeConverter.printHexBinary(data));
37 |
38 | int err = data[0x22] | (data[0x23] << 8);
39 |
40 | if (err == 0) {
41 | byte[] pl = device.decryptFromDeviceMessage(data);
42 | log.debug(this.getClass().getSimpleName() + " received bytes (decrypted): "
43 | + DatatypeConverter.printHexBinary(pl));
44 | return Arrays.copyOfRange(pl, 2, pl.length);
45 | } else {
46 | log.warn(this.getClass().getSimpleName() + " received an error: " + Integer.toHexString(err) + " / " + err);
47 | }
48 | return null;
49 | }
50 |
51 | @Override
52 | public byte getCommand() {
53 | return 0x6a;
54 | }
55 |
56 | @Override
57 | public Payload getPayload() {
58 | return new Payload() {
59 | /**
60 | * hysen thermostats require a crc16 calculated on the payload before it can be
61 | * send and a length field.
62 | *
63 | * Payload format: 2 bytes len: first byte is len, second is 0. len includes
64 | * also CRC (2 bytes_ X bytes payload 2 bytes CRC16 in ModBus format
65 | */
66 | @Override
67 | public byte[] getData() {
68 | byte[] p = getCmdBytes();
69 | byte[] cmd = new byte[p.length + 4];
70 | cmd[0] = (byte) (p.length + 2);
71 | cmd[1] = 0x00;
72 |
73 | System.arraycopy(p, 0, cmd, 2, p.length);
74 |
75 | int crc = Crc16.getCrc16(p, p.length, 0xffff);
76 | cmd[cmd.length - 2] = (byte) (crc & 0xff);
77 | cmd[cmd.length - 1] = (byte) ((crc >> 8) & 0xFF);
78 | return cmd;
79 | }
80 | };
81 | }
82 |
83 | protected abstract byte[] getCmdBytes();
84 |
85 | protected static byte getTempByte(double temp) {
86 | return (byte) ((int) (temp * 2) & 0xff);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/SP1Device.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 |
30 | package com.github.mob41.blapi;
31 |
32 | import java.io.IOException;
33 | import java.net.DatagramPacket;
34 |
35 | import javax.xml.bind.DatatypeConverter;
36 |
37 | import com.github.mob41.blapi.mac.Mac;
38 | import com.github.mob41.blapi.pkt.CmdPayload;
39 | import com.github.mob41.blapi.pkt.Payload;
40 |
41 | public class SP1Device extends BLDevice {
42 |
43 | public SP1Device(String host, Mac mac) throws IOException {
44 | super(BLDevice.DEV_SP1, BLDevice.DESC_SP1, host, mac);
45 | }
46 |
47 | public void setPower(final boolean state) throws Exception {
48 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
49 |
50 | @Override
51 | public byte getCommand() {
52 | return 0x66;
53 | }
54 |
55 | @Override
56 | public Payload getPayload() {
57 | return new Payload() {
58 |
59 | @Override
60 | public byte[] getData() {
61 | byte[] b = new byte[4];
62 | b[0] = (byte) (state ? 1 : 0);
63 | return b;
64 | }
65 |
66 | };
67 | }
68 |
69 | });
70 |
71 | byte[] data = packet.getData();
72 |
73 | log.debug("SP1 set power received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
74 |
75 | int err = data[0x22] | (data[0x23] << 8);
76 |
77 | if (err != 0) {
78 | log.warn("SP1 set power received returned err: " + Integer.toHexString(err) + " / " + err);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | Multiple important things to know before contributing to the repository.
3 |
4 | ## Reporting Issues
5 |
6 | As the library is still in heavy development stage, most things are not supposed to be stable. We are welcome to receive problems and bugs reports from users.
7 |
8 | To report a bug, a proper log recording files are required. Please kindly create a file called ```log4j.properties``` next to your application/development environment:
9 |
10 | ```java
11 | log4j.rootLogger=DEBUG, STDOUT
12 | log4j.logger.deng=DEBUG
13 | log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender
14 | log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout
15 | log4j.appender.STDOUT.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
16 | ```
17 |
18 | This file will allow the library to print debug logs to console.
19 |
20 | And try to reproduce the problem again. After that, copy the logs to a text file and attach/upload to the issue page.
21 |
22 | ## Code contributing
23 |
24 | Code contributing to the Java Broadlink library is appreciated. However, please follow the following coding style, so as to improve code readability.
25 |
26 | ### We use [K&R (1TBS (OTBS))](https://en.wikipedia.org/wiki/Indent_style#K.26R) coding style.
27 |
28 | ```java
29 | if (condition){
30 | //do stuff
31 | } else {
32 | //do else stuff
33 | }
34 |
35 | for (int i = 0; i < 10; i++){
36 | //do stuff
37 | }
38 |
39 | while(true){
40 | //do loop stuff
41 | }
42 | ```
43 |
44 | ### Use spaces instead of tabs for indentation. (1 tab for 4 spaces) [[Eclipse IDE]](https://stackoverflow.com/questions/407929/how-do-i-change-eclipse-to-use-spaces-instead-of-tabs)
45 |
46 | ### No EOF allowed in the last line of the source code. Please add a new line (```\r\n```) instead.
47 |
48 | ### Comment and describe usage on every bitwise operation
49 |
50 | ```java
51 | int test = 15; //0000 1111
52 |
53 | test |= 15 << 4; //shift 15 (00001111) left 4 position and OR "test"
54 | ```
55 |
56 | ### JavaDoc/comment for every method, including constructor, for better JavaDoc API documentation generating
57 |
58 | JavaDoc comment starts with ```/**```, appends ```*``` down on each single line and ends with ```*/```. For example:
59 |
60 | ```java
61 | /**
62 | * This is the base class of all Broadlink devices (e.g. SP1, RMPro)
63 | *
64 | * @author Anthony
65 | *
66 | */
67 | public BLDevice(){
68 | //do stuff
69 | }
70 | ```
71 |
72 | ### Comment style
73 |
74 | Don't be lazy :smile: Eclipse IDE loves automatically helps you to open a comment box like this:
75 |
76 | ```java
77 | /*
78 | * This is a dirty comment
79 | *
80 | */
81 | ```
82 |
83 | We don't allow to use this kind of commenting. It is only allowed to use to comment outside of any method to keep clean. Instead, you could use this in methods:
84 |
85 | ```java
86 | /*
87 | System.out.println("I just wanted to comment this code snippet.");
88 | System.out.println("It is much cleaner, right?");
89 | */
90 | ```
91 |
92 | Or even using the simple ```//```:
93 |
94 | ```java
95 | //System.exit(0);
96 | ```
97 |
98 |
99 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
3 | jdk:
4 | - openjdk8
5 |
6 | install: true
7 |
8 | before_install:
9 | - sudo apt-get update
10 | - sudo apt-get install gnupg gnupg2
11 | - chmod +x .travis/prepare
12 | - .travis/prepare
13 |
14 | script:
15 | - mvn cobertura:cobertura
16 |
17 | after_success:
18 | - bash <(curl -s https://codecov.io/bash)
19 |
20 | before_deploy:
21 | - ls
22 | - chmod +x .travis/deploy
23 |
24 | deploy:
25 | provider: script
26 | script: bash .travis/deploy
27 | on:
28 | branch: master
29 |
30 | after_deploy:
31 | # TODO: Auto JavaDoc Gen
32 | #- git config --global user.email "travis@travis-ci.org"
33 | #- git config --global user.name "travis-ci"
34 |
35 | branches:
36 | only:
37 | - master
38 |
39 | env:
40 | global:
41 | - secure: "Y7H9VySOaGASsbGhr55/HDhAt7EJvMslHuTYub8It/rWaG8f2DGJyfekLovB8Am/ZrW2V4BNOBzL5HaEqiAjdtTKKq9Xd6ZhqF54ZGkd6J64kMIUI+pcKwBgWLlzD8UhKrZiG3ubrfoYVFa08Sb+BhCXfPS2+SFdECb2NH47dFkZR+uqJKN18s7yQzrqvb0jabpFZgCR2dPMJfKoHMCErScV/oT875QEArHFCD1wwK7tQWTvtqWhG9BDZOMdClX4BP8TQx+nN9SNkUvyiG998/Jeu+Hm0lslTBo+bm/jMbPIu4d4YZvbDDXltcn4kuQLtfX5FcdVI9aAK0B7Ycc5t4Ci19zhGLIVzFBiQ0ER3igoSQM/F8EwsvkP1Pl3Nl7sFIhagNgQK2DLeEj7NXe56IUjY7HDl7Bo/ffq31GSjs9XqXVC3EEghilexN8LMbsjuhfGWCHlqb3/a4zw1rr8ekv2iTXBglUTCLghxqkDVFYg+7QdS77fyi+OTo9NZEm46pV5QhabD02JxeFef55uuDwuospzYXzE6KZkIxeZq1bgOtZ9PNi7bjUd4uribOE+dlO8k4+MOiWyEGNoDZhd16Q+C+j1B+Z0SuGgZbFi1F6vvAmnBkyF3jUDxiUaRJ5RVh+eX03f8R5tIhb5hqzDsTB1wXM0oWUHsgXb93VVygI="
42 | - secure: "fn+6muORgvM7G39J4bIP588OPuYijfnkcEUAQE/xPcPmJ2Q3In+XmDiuyh8lbRHZ88fU/5zGV7Zpvafjgo0dW4dxAtjZYuXArvY5cy5LSSmiElCKvptP09WLRqBRQ/DGS7NEBlTD6TDipdFFVEJOtOz2vg71eKadlUA1HCnhvGv7pOCZeWxzC4oohuPDdMoegQFBIrjHU4i7q0auIHU5hrT2UPppZnqfDMmIDMTU/hkfKxnoeW6DroBCHjCJtwEW+aKb837KgCmDiKsAhGJ/B+PJP/aqNH4ZxIPWPsmgvMIslnW9Csa8k+D95EXxUIyawyfz9AHjg/E85yRRYqhm2PCQxlKfNuTOsBd8ynBZEOr3GQB3+VzCmSTZSRKjAf2Q5aqFi6KFhs/4hg054GryO+qbSL5efO2mkTRnNFzEfOeEGR4ADC2yTVJabu824FVgk3Dcuwp7ll8U/LdUZwYsGt4nS8RW5izOlRnonFWiRDA1JpQNb5bjJVtu8ZjR0BrZpdqa9gWawzSUrxESbK6Zlyuu+4wS5VypGVhbhsAtVsSH+ZT45pDDT5EeK8Fe02f9z4TJmH4K+K6Rf7S6TXQtLXQ5r5X0Xn6OQtx5axJ5UzDbfRUs48MDMsRu3eecD5ROos/hEQOy7Mb39dUbo0grm9pC7q9STgKSilVUhiFxkiQ="
43 | - secure: "eFDjBk2mLHkMyf1nMqOSWCj/jzUAqpwrnhSovUNF/kCVuyTF0hLqtENyruj9NlbB3q8Lbtd05OhTCnJhL3Fibr/1DCgQwdLEBp0ROXE1tlIZj1r8BMY1CEVIp6ubiEW9YxIKajV+b3sLr+kQtTZ9rL7EECwRuBDhpLYGDjR+KET3608tuUdEuLrSBSsqmdIbBB2EZMHP485WnOxL1AULgOA6xWOzkkOoiNZqaIeDAEmSBo/vCB3T+KN/jAipSq9QSog76fGvNtUAki+tG7LPl/gspGyLqMRrhjFimz7Z1qJYjKxopeuodqcNuERKZZIV9LvGUsHFVaEEaswnEtl0FEsuPZRGMWe9rhZqTtBHqSgwUcnOh6Lful0wVj/3ZEZrSoX4sRRgQOb6AJ5la6MX5pZyowjr1kdhnslSW4YHG9JD28M+CysCkHqmANmjt/9Uf774hXth4cKH5itHiVkE5GQFrj3reAYGs/usGJ14RI3fzuhh7fR/zWSEknjxfVFZDpVsXpaKKnbaeDO+r4ly8odH/Y3JtjWzYPSUbedgw/wagJpO3VUVbb5VAGFqm4U19ekNMOJWeT3bXiUvH0wJsRTBOWwY5aUQK8tF4xajyqa3RiUPsDzDeaM0lXH62a0umVdCMEj1w4rf801GDV2zjWW8BVH7YGFlvzcpc5jFCOE="
44 | - secure: "s15e/Zme0CMGXMGrOV4frcUkBlBq9A8Qt0x1IE8Tl1DJvwTe5bLN4vY4raobP0vfEFjgKx5hrWJqWLBosj4b3fe6ea8v0/kAoFw+OjGBtHsWEA8qhf1K8c4/dHSc56IRpgRvF7xNAXtjp3rCWyleiUGJvDFfY/oEiUSzrdxDOgtV8uQnahtwrfTniXDsZq2GChm7/AUwTqGwROkGa0YtA9ogqgCv7Ckk3Gac1tmiuTZUGnDgqmiCIqdG2CIu8SznTCu0FkRHqO2vfAh+wyEt3ScanSCZeM983yaHv7tDLfRgh1b1VHCUxMYEhzICn32zZTGcvgmBWe2MOQWg4anUoP71pVIT7vx0VveMuatQ8/jeviLjXaRRMVz4DjLBI+E+RN77dn84XjIBgOsE0ZsRPktxIgECxM08BT/fFCLxw/1fMyNFt44P46FEC8Ly3NGxfg/74RIou24uAog52rhs93nchqQj4XCyz3MikJ3kdJCFCj/mPDLmd7Xr8urbbf7CIwNQmUoilPBeyd1eFxaD/ICC6zcxDSM7EhufetSLh0NXIHM/qvFntl81P5LjgIbYNMIleiRuzsDMIam93ppqRmxC3YMUhEYzERm09t2ra9b7kNyUJqw4JnpUjpImPIQzo8R/V17BmihI/T+/1PDCkhBwfg0qTILORsA0/4si5s0="
45 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/A1Device.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi;
30 |
31 | import java.io.IOException;
32 | import java.net.DatagramPacket;
33 |
34 | import javax.xml.bind.DatatypeConverter;
35 |
36 | import com.github.mob41.blapi.mac.Mac;
37 | import com.github.mob41.blapi.pkt.CmdPayload;
38 | import com.github.mob41.blapi.pkt.Payload;
39 |
40 | public class A1Device extends BLDevice {
41 |
42 | public A1Device(String host, Mac mac) throws IOException {
43 | super(BLDevice.DEV_A1, BLDevice.DESC_A1, host, mac);
44 | }
45 |
46 | public EnvData getSensorsData() throws Exception {
47 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
48 |
49 | @Override
50 | public byte getCommand() {
51 | return 0x6a;
52 | }
53 |
54 | @Override
55 | public Payload getPayload() {
56 | return new Payload() {
57 |
58 | @Override
59 | public byte[] getData() {
60 | byte[] b = new byte[16];
61 | b[0] = 1;
62 | return b;
63 | }
64 |
65 | };
66 | }
67 |
68 | });
69 | byte[] data = packet.getData();
70 |
71 | log.debug("A1 check sensors received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
72 |
73 | int err = data[0x22] | (data[0x23] << 8);
74 |
75 | if (err == 0) {
76 | byte[] pl = decryptFromDeviceMessage(data);
77 | log.debug("A1 check sensors received bytes (decrypted):" + DatatypeConverter.printHexBinary(pl));
78 |
79 | float temp = (float) ((pl[0x4] * 10 + pl[0x5]) / 10.0);
80 | float hum = (float) ((pl[0x6] * 10 + pl[0x7]) / 10.0);
81 | byte light = pl[0x8];
82 | byte airQuality = pl[0x0a];
83 | byte noise = pl[0xc];
84 |
85 | return new EnvData(temp, hum, light, airQuality, noise);
86 | } else {
87 | log.warn("A1 check sensors received an error: " + Integer.toHexString(err) + " / " + err);
88 | return null;
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/mac/MacFormatException.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.mac;
30 |
31 | import com.github.mob41.blapi.ex.BLApiRuntimeException;
32 |
33 | /**
34 | * MacFormatException is thrown where the MAC address String /
35 | * bytes array specified does not have a valid format. (Total 48 bit / 6 bytes
36 | * 00:00:00:00:00:00)
37 | *
38 | * @author Anthony
39 | *
40 | */
41 | public class MacFormatException extends BLApiRuntimeException {
42 |
43 | /**
44 | *
45 | */
46 | private static final long serialVersionUID = -4391144827334864769L;
47 |
48 | private static final String INVALID_MAC_FORMAT = "Invalid MAC address String format";
49 |
50 | private static final String INCORRECT_MAC_ADDR_BYTES = "Incorrect MAC address bytes";
51 |
52 | /**
53 | * Creates the exception with a "Invalid MAC address String format" message.
54 | *
55 | * @param arg0
56 | * the invalid MAC address String
57 | */
58 | public MacFormatException(String arg0) {
59 | super(INVALID_MAC_FORMAT + ": " + arg0);
60 | }
61 |
62 | /**
63 | * Creates the exception with a "Invalid MAC address String format" message,
64 | * and a Throwable
65 | *
66 | * @param arg0
67 | * the invalid MAC address String
68 | * @param arg1
69 | * the Throwable
70 | */
71 | public MacFormatException(String arg0, Throwable arg1) {
72 | super(INVALID_MAC_FORMAT + ": " + arg0, arg1);
73 | }
74 |
75 | /**
76 | * Creates the exception with a "Incorrect MAC address bytes"
77 | *
78 | * @param bytes
79 | * the bytes array with incorrect length (not equal to 6)
80 | */
81 | public MacFormatException(byte[] bytes) {
82 | super(INCORRECT_MAC_ADDR_BYTES + ": " + (bytes != null ? bytes.length : "null") + "/6: "
83 | + bytesArrToHexStrArr(bytes));
84 | }
85 |
86 | private static String[] bytesArrToHexStrArr(byte[] ba) {
87 | if (ba == null) {
88 | return null;
89 | }
90 |
91 | String[] outa = new String[ba.length];
92 | for (int i = 0; i < outa.length; i++) {
93 | outa[i] = Integer.toHexString(ba[i]);
94 | }
95 |
96 | return outa;
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/EnvData.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi;
30 |
31 | public class EnvData {
32 |
33 | public static final byte LIGHT_DARK = 0x00;
34 |
35 | public static final byte LIGHT_DIM = 0x01;
36 |
37 | public static final byte LIGHT_NORMAL = 0x02;
38 |
39 | public static final byte LIGHT_BRIGHT = 0x03;
40 |
41 | public static final byte AIR_QUALITY_EXCELLENT = 0x00;
42 |
43 | public static final byte AIR_QUALITY_GOOD = 0x01;
44 |
45 | public static final byte AIR_QUALITY_NORMAL = 0x02;
46 |
47 | public static final byte AIR_QUALITY_BAD = 0x03;
48 |
49 | public static final byte NOISE_QUIET = 0x00;
50 |
51 | public static final byte NOISE_NORMAL = 0x01;
52 |
53 | public static final byte NOISE_NOISY = 0x02;
54 |
55 | private final float temp;
56 |
57 | private final float humidity;
58 |
59 | private final byte light;
60 |
61 | private final byte airQuality;
62 |
63 | private final byte noise;
64 |
65 | protected EnvData(float temp, float hum, byte light, byte airQuality, byte noise){
66 | this.temp = temp;
67 | this.humidity = hum;
68 | this.light = light;
69 | this.airQuality = airQuality;
70 | this.noise = noise;
71 | }
72 |
73 | public float getTemp() {
74 | return temp;
75 | }
76 |
77 | public float getHumidity() {
78 | return humidity;
79 | }
80 |
81 | public byte getLight() {
82 | return light;
83 | }
84 |
85 | public String getLightDescription(){
86 | switch (light){
87 | case LIGHT_DARK:
88 | return "dark";
89 | case LIGHT_DIM:
90 | return "dim";
91 | case LIGHT_NORMAL:
92 | return "normal";
93 | case LIGHT_BRIGHT:
94 | return "bright";
95 | default:
96 | return "unknown";
97 | }
98 | }
99 |
100 | public byte getAirQuality() {
101 | return airQuality;
102 | }
103 |
104 | public String getAirQualityDescription(){
105 | switch (light){
106 | case AIR_QUALITY_EXCELLENT:
107 | return "excellent";
108 | case AIR_QUALITY_GOOD:
109 | return "good";
110 | case AIR_QUALITY_NORMAL:
111 | return "normal";
112 | case AIR_QUALITY_BAD:
113 | return "bad";
114 | default:
115 | return "unknown";
116 | }
117 | }
118 |
119 | public byte getNoise() {
120 | return noise;
121 | }
122 |
123 | public String getNoiseDescription(){
124 | switch (light){
125 | case NOISE_QUIET:
126 | return "quiet";
127 | case NOISE_NORMAL:
128 | return "normal";
129 | case NOISE_NOISY:
130 | return "noisy";
131 | default:
132 | return "unknown";
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/SP2Device.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 |
30 | package com.github.mob41.blapi;
31 |
32 | import java.io.IOException;
33 | import java.net.DatagramPacket;
34 |
35 | import javax.xml.bind.DatatypeConverter;
36 |
37 | import com.github.mob41.blapi.mac.Mac;
38 | import com.github.mob41.blapi.pkt.CmdPayload;
39 | import com.github.mob41.blapi.pkt.Payload;
40 |
41 | public class SP2Device extends BLDevice {
42 |
43 | protected SP2Device(short deviceType, String deviceDesc, String host, Mac mac) throws IOException {
44 | super(deviceType, deviceDesc, host, mac);
45 | }
46 |
47 | public SP2Device(String host, Mac mac) throws IOException{
48 | super(BLDevice.DEV_SP2, BLDevice.DESC_SP2, host, mac);
49 | }
50 |
51 | public void setState(final boolean state) throws Exception {
52 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
53 |
54 | @Override
55 | public byte getCommand() {
56 | return 0x6a;
57 | }
58 |
59 | @Override
60 | public Payload getPayload() {
61 | return new Payload() {
62 |
63 | @Override
64 | public byte[] getData() {
65 | byte[] b = new byte[16];
66 | b[0] = (byte) 2;
67 | b[4] = (byte) (state ? 1 : 0);
68 | return b;
69 | }
70 |
71 | };
72 | }
73 |
74 | });
75 |
76 | byte[] data = packet.getData();
77 |
78 | log.debug("SP2 set state received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
79 |
80 | int err = data[0x22] | (data[0x23] << 8);
81 |
82 | if (err != 0) {
83 | log.warn("SP2 set state received returned err: " + Integer.toHexString(err) + " / " + err);
84 | }
85 | }
86 |
87 | public boolean getState() throws Exception {
88 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
89 |
90 | @Override
91 | public byte getCommand() {
92 | return 0x6a;
93 | }
94 |
95 | @Override
96 | public Payload getPayload() {
97 | return new Payload() {
98 |
99 | @Override
100 | public byte[] getData() {
101 | byte[] b = new byte[16];
102 | b[0] = 1;
103 | return b;
104 | }
105 |
106 | };
107 | }
108 |
109 | });
110 | byte[] data = packet.getData();
111 |
112 | log.debug("SP2 get state received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
113 |
114 | int err = data[0x22] | (data[0x23] << 8);
115 |
116 | if (err == 0) {
117 | byte[] pl = decryptFromDeviceMessage(data);
118 | log.debug("SP2 get state received bytes (decrypted): " + DatatypeConverter.printHexBinary(pl));
119 | return pl[0x4] == 1 ? true : false;
120 | } else {
121 | log.warn("SP2 get state received an error: " + Integer.toHexString(err) + " / " + err);
122 | }
123 |
124 | return false;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/Crc16.java:
--------------------------------------------------------------------------------
1 | package com.github.mob41.blapi.pkt;
2 |
3 |
4 | /**
5 | * Stolen from org.eclipse.kura.protocol.modbus.Crc16 (Eclipse kura project)
6 | *
7 | * Used to calculate the CRC-16 (cyclical redundancy check) for an array of bytes.
8 | */
9 | public class Crc16 {
10 |
11 | private Crc16() {
12 | };
13 |
14 | /**
15 | * calculate the crc for the passed buffer
16 | *
17 | * @param buff
18 | * byte array to calculate CRC of
19 | * @param buffLen
20 | * number of bytes in array to calculate against
21 | * @param crcSeed
22 | * starting seed for CRC calculation
23 | * @return CRC16 as calculated for buff
24 | */
25 | public static int getCrc16(byte[] buff, int buffLen, int crcSeed) {
26 | int hi, lo, tmp;
27 |
28 | lo = crcSeed & 0x0ff;
29 | hi = crcSeed >> 8 & 0x0ff;
30 |
31 | for (int i = 0; i < buffLen; i++) {
32 | tmp = (lo ^ buff[i]) & 0x0ff;
33 | lo = hi ^ abCrcTbl2[tmp];
34 | hi = abCrcTbl1[tmp];
35 | }
36 | return lo + (hi << 8);
37 | }
38 |
39 | private final static int[] abCrcTbl1 = { 0x000, 0x0C0, 0x0C1, 0x001, 0x0C3, 0x003, 0x002, 0x0C2, 0x0C6, 0x006,
40 | 0x007, 0x0C7, 0x005, 0x0C5, 0x0C4, 0x004, 0x0CC, 0x00C, 0x00D, 0x0CD, 0x00F, 0x0CF, 0x0CE, 0x00E, 0x00A,
41 | 0x0CA, 0x0CB, 0x00B, 0x0C9, 0x009, 0x008, 0x0C8, 0x0D8, 0x018, 0x019, 0x0D9, 0x01B, 0x0DB, 0x0DA, 0x01A,
42 | 0x01E, 0x0DE, 0x0DF, 0x01F, 0x0DD, 0x01D, 0x01C, 0x0DC, 0x014, 0x0D4, 0x0D5, 0x015, 0x0D7, 0x017, 0x016,
43 | 0x0D6, 0x0D2, 0x012, 0x013, 0x0D3, 0x011, 0x0D1, 0x0D0, 0x010, 0x0F0, 0x030, 0x031, 0x0F1, 0x033, 0x0F3,
44 | 0x0F2, 0x032, 0x036, 0x0F6, 0x0F7, 0x037, 0x0F5, 0x035, 0x034, 0x0F4, 0x03C, 0x0FC, 0x0FD, 0x03D, 0x0FF,
45 | 0x03F, 0x03E, 0x0FE, 0x0FA, 0x03A, 0x03B, 0x0FB, 0x039, 0x0F9, 0x0F8, 0x038, 0x028, 0x0E8, 0x0E9, 0x029,
46 | 0x0EB, 0x02B, 0x02A, 0x0EA, 0x0EE, 0x02E, 0x02F, 0x0EF, 0x02D, 0x0ED, 0x0EC, 0x02C, 0x0E4, 0x024, 0x025,
47 | 0x0E5, 0x027, 0x0E7, 0x0E6, 0x026, 0x022, 0x0E2, 0x0E3, 0x023, 0x0E1, 0x021, 0x020, 0x0E0, 0x0A0, 0x060,
48 | 0x061, 0x0A1, 0x063, 0x0A3, 0x0A2, 0x062, 0x066, 0x0A6, 0x0A7, 0x067, 0x0A5, 0x065, 0x064, 0x0A4, 0x06C,
49 | 0x0AC, 0x0AD, 0x06D, 0x0AF, 0x06F, 0x06E, 0x0AE, 0x0AA, 0x06A, 0x06B, 0x0AB, 0x069, 0x0A9, 0x0A8, 0x068,
50 | 0x078, 0x0B8, 0x0B9, 0x079, 0x0BB, 0x07B, 0x07A, 0x0BA, 0x0BE, 0x07E, 0x07F, 0x0BF, 0x07D, 0x0BD, 0x0BC,
51 | 0x07C, 0x0B4, 0x074, 0x075, 0x0B5, 0x077, 0x0B7, 0x0B6, 0x076, 0x072, 0x0B2, 0x0B3, 0x073, 0x0B1, 0x071,
52 | 0x070, 0x0B0, 0x050, 0x090, 0x091, 0x051, 0x093, 0x053, 0x052, 0x092, 0x096, 0x056, 0x057, 0x097, 0x055,
53 | 0x095, 0x094, 0x054, 0x09C, 0x05C, 0x05D, 0x09D, 0x05F, 0x09F, 0x09E, 0x05E, 0x05A, 0x09A, 0x09B, 0x05B,
54 | 0x099, 0x059, 0x058, 0x098, 0x088, 0x048, 0x049, 0x089, 0x04B, 0x08B, 0x08A, 0x04A, 0x04E, 0x08E, 0x08F,
55 | 0x04F, 0x08D, 0x04D, 0x04C, 0x08C, 0x044, 0x084, 0x085, 0x045, 0x087, 0x047, 0x046, 0x086, 0x082, 0x042,
56 | 0x043, 0x083, 0x041, 0x081, 0x080, 0x040 };
57 |
58 | private final static int[] abCrcTbl2 = { 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
59 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
60 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
61 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
62 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
63 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
64 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
65 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
66 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
67 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
68 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
69 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
70 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
71 | 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
72 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x00, 0x0C1, 0x081,
73 | 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x00, 0x0C1, 0x081, 0x40, 0x01, 0x0C0, 0x080, 0x41, 0x01, 0x0C0, 0x080,
74 | 0x41, 0x00, 0x0C1, 0x081, 0x40 };
75 | }
76 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
29 | 2.ii
65 | import com.github.mob41.blapi.mac.Mac; //Necessary if using 2.ii
66 | ```
67 |
68 | 2. Creating/Discovering ```BLDevice``` instances by two methods:
69 |
70 | ```java
71 | //
72 | // === Method 1. By Discovering Devices In Local Network ===
73 | //
74 |
75 | BLDevice[] devs = BLDevice.discoverDevices(); //Default with 10000 ms (10 sec) timeout, search for multiple devices
76 |
77 | //BLDevice[] devs = BLDevice.discoverDevices(0); //No timeout will block the thread and search for one device only
78 | //BLDevice[] devs = BLDevice.discoverDevices(5000); //With 5000 ms (5 sec) timeout
79 |
80 | //The BLDevice[] array stores the found devices in the local network
81 |
82 | System.out.println("Number of devices: " + devs.length);
83 |
84 | BLDevice blDevice = null;
85 | for (BLDevice dev : devs){
86 | System.out.println("Type: " + Integer.toHexString(dev.getDeviceType()) + " Host: " + dev.getHost() + " Mac: " + dev.getMac());
87 | }
88 |
89 | //BLDevice dev = devs[0]
90 |
91 | //
92 | // === Method 2. Create a "RM2Device" or another "BLDevice" child according to your device type ===
93 | //
94 |
95 | BLDevice dev = new RM2Device("192.168.1.123", new Mac("01:12:23:34:43:320"));
96 | //~do stuff
97 | //dev.auth();
98 | ```
99 |
100 | 3. Before any commands like ```getTemp()``` and ```enterLearning()```, ```BLDevice.auth()``` must be ran to connect and authenticate with the Broadlink device.
101 |
102 | ```java
103 | boolean success = dev.auth();
104 | System.out.println("Auth status: " + (success ? "Success!" : "Failed!"));
105 | ```
106 |
107 | 3. Every BLDevice has its very own methods. Please refer to their own source code in the repository (as the main documentation still not completed...). Here's an example:
108 |
109 | ```java
110 | if (dev instanceof RM2Device){
111 | RM2Device rm2 = (RM2Device) dev;
112 |
113 | boolean success = rm2.enterLearning();
114 | System.out.println("Enter Learning status: " + (success ? "Success!" : "Failed!"));
115 |
116 | float temp = rm2.getTemp();
117 | System.out.println("Current temperature reported from RM2: " + temp + " degrees");
118 | } else {
119 | System.out.println("The \"dev\" is not a RM2Device instance.");
120 | }
121 | ```
122 |
123 | ## License
124 |
125 | [tl;dr](https://tldrlegal.com/license/mit-license) This project is licensed under the MIT License.
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/mac/Mac.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.mac;
30 |
31 |
32 | /**
33 | * A class that handles a MAC address in String and bytes array format
34 | *
35 | * @author Anthony
36 | */
37 | public class Mac {
38 |
39 | private final byte[] mac;
40 |
41 | /**
42 | * Creates an instance representing the MAC address
43 | *
44 | * @param macBytes The 6-byte MAC address in byte array
45 | * @throws MacFormatException If the MAC address bytes array specified is not with length 6
46 | * or null
47 | */
48 | public Mac(byte[] macBytes) throws MacFormatException {
49 | if (!isMACValid(macBytes)) {
50 | throw new MacFormatException(macBytes);
51 | }
52 | mac = macBytes;
53 | }
54 |
55 | /**
56 | * Creates an instance representing the MAC address
57 | *
58 | * @param macStr MAC address represented in String seperated by cottons (
59 | * :) (e.g. 00:00:00:00:00:00)
60 | * @throws MacFormatException If the MAC address does not have a valid format:
61 | *
62 | * does not have a hex (e.g. 0f) in the macStr, or,
63 | * does not have 6 hex separated by cottons (:), or, a
64 | * null is specified
65 | */
66 | public Mac(String macStr) throws MacFormatException {
67 | mac = macStrToBytes(macStr);
68 | }
69 |
70 | /**
71 | * Returns the MAC address in bytes array
72 | *
73 | * @return MAC address in bytes array
74 | */
75 | public byte[] getMac() {
76 | return mac;
77 | }
78 |
79 | /**
80 | * Returns the MAC address represented in String
81 | *
82 | * @return MAC address in String
83 | */
84 | public String getMacString() {
85 | return bytesToMacStr(mac);
86 | }
87 |
88 | /**
89 | * Converts MAC address String into bytes
90 | *
91 | * @param macStr The 6-byte MAC Address (00:00:00:00:00:00) in String separated
92 | * by cottons (:)
93 | * @return Converted MAC Address in bytes
94 | * @throws MacFormatException If the MAC address does not have a valid format:
95 | *
96 | * does not have a hex (e.g. 0f) in the macStr, or,
97 | * does not have 6 hex separated by cottons (:), or, a
98 | * null is specified
99 | */
100 | public static byte[] macStrToBytes(String macStr) throws MacFormatException {
101 | if (macStr == null) {
102 | throw new MacFormatException(macStr);
103 | }
104 |
105 | String[] macs = macStr.split(":");
106 |
107 | if (macs.length != 6) {
108 | throw new MacFormatException(macStr);
109 | }
110 |
111 | byte[] bout = new byte[6];
112 | for (int i = 0; i < macs.length; i++) {
113 | try {
114 | Integer hex = Integer.parseInt(macs[i], 16);
115 | bout[i] = hex.byteValue();
116 | } catch (NumberFormatException e) {
117 | throw new MacFormatException(macStr, e);
118 | }
119 | }
120 |
121 | return bout;
122 | }
123 |
124 | /**
125 | * Returns whether the specified MAC bytes array is valid with the following
126 | * conditions:
127 | *
128 | * 1. macBytes not null
129 | * 2. macBytes's length is equal to 6
130 | *
131 | * @param macBytes The byte array to be validated
132 | * @return The validation result
133 | */
134 | public static boolean isMACValid(byte[] macBytes) {
135 | return macBytes != null && macBytes.length == 6;
136 | }
137 |
138 | /**
139 | * Converts MAC address bytes into String
140 | *
141 | * @param macBytes The 6-byte MAC Address in byte array
142 | * @return A MAC address String converted from the byte array
143 | * @throws MacFormatException If the MAC address bytes array specified is not with length 6
144 | * or null
145 | */
146 | public static String bytesToMacStr(byte[] macBytes) throws MacFormatException {
147 | if (!isMACValid(macBytes)) {
148 | throw new MacFormatException(macBytes);
149 | }
150 |
151 | String str = "";
152 |
153 | for (int i = 0; i < macBytes.length; i++) {
154 | String hexStr = String.format("%02x", macBytes[i]);
155 | str += hexStr;
156 |
157 | if (i != macBytes.length - 1) {
158 | str += ':';
159 | }
160 | }
161 | return str;
162 | }
163 |
164 | @Override
165 | public String toString() {
166 | return getMacString();
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/CmdPacket.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt;
30 |
31 | import javax.xml.bind.DatatypeConverter;
32 |
33 | import org.slf4j.Logger;
34 | import org.slf4j.LoggerFactory;
35 |
36 | import com.github.mob41.blapi.BLDevice;
37 | import com.github.mob41.blapi.ex.BLApiRuntimeException;
38 | import com.github.mob41.blapi.mac.Mac;
39 | import com.github.mob41.blapi.pkt.auth.AES;
40 |
41 | /**
42 | * This constructs a byte array with the format of a command to the Broadlink
43 | * device
44 | *
45 | * @author Anthony
46 | *
47 | */
48 | public class CmdPacket implements Packet {
49 |
50 | private static final Logger log = LoggerFactory.getLogger(CmdPacket.class);
51 |
52 | private final byte[] data;
53 |
54 | /**
55 | * Constructs a command packet
56 | *
57 | * @param targetMac
58 | * Target Broadlink device MAC address
59 | * @param count
60 | * Count of packets sent (provided by BLDevice sendPkt())
61 | * @param id
62 | * This BLDevice ID provided by the Broadlink device. It is
63 | * {0,0,0,0} if auth() not ran
64 | * @param aesInstance
65 | * The AES encrypt/decrypt instance
66 | * @param cmdPayload
67 | * The data to be sent
68 | */
69 | public CmdPacket(Mac targetMac, int count, byte[] id, AES aesInstance, CmdPayload cmdPayload) {
70 |
71 | byte cmd = cmdPayload.getCommand();
72 | byte[] payload = cmdPayload.getPayload().getData();
73 | byte[] headerdata;
74 |
75 | log.debug("Constructor CmdPacket starts");
76 | log.debug("count=" + count + " cmdPayload.cmd=" + Integer.toHexString(cmd) + " payload.len=" + payload.length);
77 |
78 | count = (count + 1) & 0xffff; // increased by the sendPkt()
79 |
80 | log.debug("New count: " + count + " (added by 1)");
81 | log.debug("Creating byte array with data");
82 |
83 | headerdata = new byte[BLDevice.DEFAULT_BYTES_SIZE];
84 | for (int i = 0; i < headerdata.length; i++) {
85 | headerdata[i] = 0x00;
86 | }
87 |
88 | headerdata[0x00] = 0x5a;
89 | headerdata[0x01] = (byte) 0xa5;
90 | headerdata[0x02] = (byte) 0xaa;
91 | headerdata[0x03] = 0x55;
92 | headerdata[0x04] = 0x5a;
93 | headerdata[0x05] = (byte) 0xa5;
94 | headerdata[0x06] = (byte) 0xaa;
95 | headerdata[0x07] = 0x55;
96 |
97 | headerdata[0x24] = 0x2a;
98 | headerdata[0x25] = 0x27;
99 | headerdata[0x26] = cmd;
100 |
101 | headerdata[0x28] = (byte) (count & 0xff);
102 | headerdata[0x29] = (byte) (count >> 8);
103 |
104 | byte[] mac = targetMac.getMac();
105 |
106 | headerdata[0x2a] = mac[0];
107 | headerdata[0x2b] = mac[1];
108 | headerdata[0x2c] = mac[2];
109 | headerdata[0x2d] = mac[3];
110 | headerdata[0x2e] = mac[4];
111 | headerdata[0x2f] = mac[5];
112 |
113 | headerdata[0x30] = id[0];
114 | headerdata[0x31] = id[1];
115 | headerdata[0x32] = id[2];
116 | headerdata[0x33] = id[3];
117 |
118 | // pad the payload for AES encryption
119 | byte[] payloadPad = null;
120 | if(payload.length > 0) {
121 | int numpad = 16 - (payload.length % 16);
122 |
123 | payloadPad = new byte[payload.length+numpad];
124 | for(int i = 0; i < payloadPad.length; i++) {
125 | if(i < payload.length)
126 | payloadPad[i] = payload[i];
127 | else
128 | payloadPad[i] = 0x00;
129 | }
130 | }
131 |
132 | log.debug("Running checksum for un-encrypted payload");
133 |
134 | int checksumpayload = 0xbeaf;
135 | for (int i = 0; i < payloadPad.length; i++) {
136 | checksumpayload = checksumpayload + Byte.toUnsignedInt(payloadPad[i]);
137 | checksumpayload = checksumpayload & 0xffff;
138 | }
139 |
140 | headerdata[0x34] = (byte) (checksumpayload & 0xff);
141 | headerdata[0x35] = (byte) (checksumpayload >> 8);
142 |
143 | log.debug("Un-encrypted payload checksum: " + Integer.toHexString(checksumpayload));
144 |
145 | try {
146 | log.debug("Encrypting payload");
147 |
148 | payload = aesInstance.encrypt(payloadPad);
149 | log.debug("Encrypted payload bytes: {}", DatatypeConverter.printHexBinary(payload));
150 |
151 | log.debug("Encrypted. len=" + payload.length);
152 | } catch (Exception e) {
153 | log.error("Cannot encrypt payload! Aborting", e);
154 | throw new BLApiRuntimeException("Cannot encrypt payload", e);
155 | }
156 |
157 | data = new byte[BLDevice.DEFAULT_BYTES_SIZE + payload.length];
158 |
159 | for (int i = 0; i < headerdata.length; i++) {
160 | data[i] = headerdata[i];
161 | }
162 |
163 | for (int i = 0; i < payload.length; i++) {
164 | data[i + BLDevice.DEFAULT_BYTES_SIZE] = payload[i];
165 | }
166 |
167 | log.debug("Running whole packet checksum");
168 |
169 | int checksumpkt = 0xbeaf;
170 | for (int i = 0; i < data.length; i++) {
171 | checksumpkt = checksumpkt + Byte.toUnsignedInt(data[i]);
172 | checksumpkt = checksumpkt & 0xffff;
173 | // log.debug("index: " + i + ", data byte: " + Byte.toUnsignedInt(data[i]) + ", checksum: " + checksumpkt);
174 | }
175 |
176 | log.debug("Whole packet checksum: " + Integer.toHexString(checksumpkt));
177 |
178 | data[0x20] = (byte) (checksumpkt & 0xff);
179 | data[0x21] = (byte) (checksumpkt >> 8);
180 |
181 | log.debug("End of CmdPacket constructor");
182 | }
183 |
184 | @Override
185 | public byte[] getData() {
186 | return data;
187 | }
188 |
189 | }
190 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/MP1Device.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 |
30 | package com.github.mob41.blapi;
31 |
32 | import java.io.IOException;
33 | import java.net.DatagramPacket;
34 |
35 | import javax.xml.bind.DatatypeConverter;
36 |
37 | import com.github.mob41.blapi.mac.Mac;
38 | import com.github.mob41.blapi.pkt.CmdPayload;
39 | import com.github.mob41.blapi.pkt.Payload;
40 |
41 | public class MP1Device extends BLDevice {
42 | /**
43 | * Generic way to create a MP1Device
44 | * @param deviceType Device Type
45 | * @param deviceDesc Friendly device description
46 | * @param host The target Broadlink hostname
47 | * @param mac The target Broadlink MAC address
48 | * @throws IOException Problems on constructing socket
49 | */
50 | protected MP1Device(short deviceType, String deviceDesc, String host, Mac mac) throws IOException{
51 | super(deviceType, deviceDesc, host, mac);
52 | }
53 |
54 | /**
55 | * Creates a MP1Device client instance
56 | *
57 | * @param host
58 | * The target Broadlink hostname
59 | * @param mac
60 | * The target Broadlink MAC address
61 | * @throws IOException
62 | * Problems on constructing socket
63 | */
64 | public MP1Device(String host, Mac mac) throws IOException {
65 | super(BLDevice.DEV_MP1, BLDevice.DESC_MP1, host, mac);
66 | }
67 |
68 | /**
69 | * Set the power state
70 | * @param sid The SID
71 | * @param state Power State
72 | * @throws Exception
73 | */
74 | public void setState(int sid, boolean state) throws Exception {
75 | int sid_mask = 0x01 << (sid - 1);
76 | setStateMask(sid_mask, state);
77 | }
78 |
79 | private void setStateMask(final int sid_mask, final boolean state) throws Exception {
80 | // """Sets the power state of the smart power strip."""
81 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
82 |
83 | @Override
84 | public byte getCommand() {
85 | return 0x6a;
86 | }
87 |
88 | @Override
89 | public Payload getPayload() {
90 | return new Payload() {
91 |
92 | @Override
93 | public byte[] getData() {
94 | byte[] b = new byte[16];
95 | b[0x00] = (byte) (0x0d);
96 | b[0x02] = (byte) (0xa5);
97 | b[0x03] = (byte) (0xa5);
98 | b[0x04] = (byte) (0x5a);
99 | b[0x05] = (byte) (0x5a);
100 | b[0x06] = (byte) (0xb2 + (state ? (sid_mask << 1) : sid_mask));
101 | b[0x07] = (byte) (0xc0);
102 | b[0x08] = (byte) (0x02);
103 | b[0x0a] = (byte) (0x03);
104 | b[0x0d] = (byte) (sid_mask);
105 | b[0x0e] = (byte) (state ? sid_mask : 0);
106 | return b;
107 | }
108 |
109 | };
110 | }
111 |
112 | });
113 |
114 | byte[] data = packet.getData();
115 |
116 | int err = data[0x22] | (data[0x23] << 8);
117 |
118 | if (err == 0) {
119 | log.debug("MP1 set state mask received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
120 | } else {
121 | log.warn("MP1 set state mask received returned err: " + Integer.toHexString(err) + " / " + err);
122 | }
123 | }
124 |
125 | public boolean getStateByIndex(int index) throws Exception{
126 | return getStates()[index];
127 | }
128 |
129 | public boolean[] getStates() throws Exception {
130 | // """Returns the power state of the smart power strip."""
131 | byte state = getStatesRaw();
132 | boolean[] data = new boolean[4];
133 | data[0] = ((state & 0x01) != 0) ? true : false;
134 | data[1] = ((state & 0x02) != 0) ? true : false;
135 | data[2] = ((state & 0x04) != 0) ? true : false;
136 | data[3] = ((state & 0x08) != 0) ? true : false;
137 | return data;
138 | }
139 |
140 | private byte getStatesRaw() throws Exception {
141 | // """Returns the power state of the smart power strip in raw format."""
142 | DatagramPacket packet = sendCmdPkt(new CmdPayload() {
143 |
144 | @Override
145 | public byte getCommand() {
146 | return 0x6a;
147 | }
148 |
149 | @Override
150 | public Payload getPayload() {
151 | return new Payload() {
152 |
153 | @Override
154 | public byte[] getData() {
155 | byte[] b = new byte[16];
156 | b[0x00] = (byte) (0x0a);
157 | b[0x02] = (byte) (0xa5);
158 | b[0x03] = (byte) (0xa5);
159 | b[0x04] = (byte) (0x5a);
160 | b[0x05] = (byte) (0x5a);
161 | b[0x06] = (byte) (0xae);
162 | b[0x07] = (byte) (0xc0);
163 | b[0x08] = (byte) (0x01);
164 | return b;
165 | }
166 |
167 | };
168 | }
169 |
170 | });
171 |
172 | byte[] data = packet.getData();
173 |
174 | log.debug("MP1 get states raw received encrypted bytes: " + DatatypeConverter.printHexBinary(data));
175 |
176 | int err = data[0x22] | (data[0x23] << 8);
177 |
178 | if (err == 0) {
179 | byte[] pl = decryptFromDeviceMessage(data);
180 | log.debug("MP1 get states raw received bytes (decrypted): " + DatatypeConverter.printHexBinary(pl));
181 | byte state = 0;
182 | if (pl[0x3c] >= 48 && pl[0x3c] <= 57) {
183 | String decodeValue1;
184 | decodeValue1 = String.valueOf(pl[0x46]);
185 | state = Short.decode(decodeValue1).byteValue();
186 | } else {
187 | state = pl[0x46];
188 | }
189 | return state;
190 | } else {
191 | log.warn("MP1 get states raw received an error: " + Integer.toHexString(err) + " / " + err);
192 | }
193 | return 0;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/dev/hysen/BaseStatusInfo.java:
--------------------------------------------------------------------------------
1 | package com.github.mob41.blapi.dev.hysen;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | /**
7 | * Base set of status codes
8 | *
9 | * Adapted from https://github.com/mjg59/python-broadlink
10 | *
11 | * @author alpapad
12 | */
13 | public class BaseStatusInfo {
14 | /**
15 | * The specific logger for this class
16 | */
17 | protected static final Logger log = LoggerFactory.getLogger(BaseStatusInfo.class);
18 | // remote_lock
19 | protected final boolean remoteLock;
20 | protected final boolean power;
21 | protected final boolean active;
22 | // temp_manual
23 | protected final boolean manualTemp;
24 | // room_temp
25 | protected final double roomTemp;
26 | // thermostat_temp
27 | protected final double thermostatTemp;
28 | // auto_mode
29 | protected final boolean autoMode;
30 | // loop_mode
31 | protected final LoopMode loopMode;
32 | // sensor
33 | protected final SensorControl sensorControl;
34 | // osv
35 | protected final short osv;
36 | // dif
37 | protected final short dif;
38 | // svh
39 | protected final short svh;
40 | // svl
41 | protected final short svl;
42 | // room_temp_adj
43 | protected final double roomTempAdjustment;
44 | // fre
45 | protected final AntiFreezing antiFreezing;
46 | // pon
47 | protected final PowerOnMemory powerOnMemory;
48 |
49 | // unsure
50 | protected final short fac;
51 | // external_temp
52 | protected final double externalTemp;
53 |
54 | private static String bytesToString(byte[] hashInBytes) {
55 |
56 | StringBuilder sb = new StringBuilder();
57 | for (byte b : hashInBytes) {
58 | sb.append(String.format("%04d ", b));
59 | }
60 | return sb.toString();
61 |
62 | }
63 |
64 | protected BaseStatusInfo(byte[] payload) {
65 | log.debug("payload: {}",bytesToString(payload));
66 | this.remoteLock = byteToBool((byte) (payload[3] & 0x1));
67 | this.power = byteToBool((byte) (payload[4] & 1));
68 | this.active = byteToBool((byte) ((payload[4] >> 4) & 1));
69 | this.manualTemp = byteToBool((byte) ((payload[4] >> 6) & 1));
70 | this.roomTemp = (payload[5] & 0xff) / 2.0;
71 | this.thermostatTemp = (payload[6] & 0xff) / 2.0;
72 | this.autoMode = (byte)(payload[7] & 15) != 0;
73 | this.loopMode = LoopMode.fromValue((byte) (((payload[7] >> 4)) - 1));
74 | this.sensorControl = SensorControl.fromValue(payload[8]);
75 | this.osv = payload[9];
76 | this.dif = payload[10];
77 | this.svh = payload[11];
78 | this.svl = payload[12];
79 | double tempAdj = (((payload[13] << 8) + payload[14]) / 2.0);
80 | if (tempAdj > 32767) {
81 | tempAdj = (32767 - tempAdj);
82 | }
83 | this.roomTempAdjustment = tempAdj;
84 |
85 | this.antiFreezing = AntiFreezing.fromValue(payload[15]);
86 | this.powerOnMemory = PowerOnMemory.fromValue(payload[16]);
87 | this.fac = payload[17];
88 | this.externalTemp = (payload[18] & 255) / 2.0;
89 | }
90 |
91 | public boolean getRemoteLock() {
92 | return remoteLock;
93 | }
94 |
95 | public boolean getPower() {
96 | return power;
97 | }
98 |
99 | public boolean getActive() {
100 | return active;
101 | }
102 |
103 | public boolean getManualTemp() {
104 | return manualTemp;
105 | }
106 |
107 | public double getRoomTemp() {
108 | return roomTemp;
109 | }
110 |
111 | public double getThermostatTemp() {
112 | return thermostatTemp;
113 | }
114 |
115 | public boolean getAutoMode() {
116 | return autoMode;
117 | }
118 |
119 | /**
120 | * loopMode refers to index in [ "12345,67", "123456,7", "1234567" ] E.g.
121 | * loop_mode = 0 ("12345,67") means Saturday and Sunday follow the "weekend"
122 | * schedule loop_mode = 2 ("1234567") means every day (including Saturday and
123 | * Sunday) follows the "weekday" schedule
124 | *
125 | * @return
126 | */
127 | public LoopMode getLoopMode() {
128 | return loopMode;
129 | }
130 |
131 | /**
132 | * Sensor control option 0:internal sensor
133 | *
134 | * 1:external sensor
135 | *
136 | * 2:internal control temperature,external limit temperature
137 | *
138 | * default: 0:internal sensor
139 | *
140 | * @return
141 | */
142 | public SensorControl getSensorControl() {
143 | return sensorControl;
144 | }
145 |
146 | /**
147 | * Limit temperature value of external sensor
148 | *
149 | * values: 5-99ºC
150 | *
151 | * default: 42ºC
152 | *
153 | */
154 |
155 | public short getOsv() {
156 | return osv;
157 | }
158 |
159 | /**
160 | * Return difference of limit temperature value of external sensor
161 | *
162 | *
163 | * values: 1-9ºC
164 | *
165 | * default: 2ºC
166 | *
167 | * @return
168 | */
169 | public short getDif() {
170 | return dif;
171 | }
172 |
173 | /**
174 | * Set upper limit temperature value
175 | *
176 | *
177 | * values: 5-99ºC
178 | *
179 | * default: 35ºC
180 | *
181 | * @return
182 | */
183 | public short getSvh() {
184 | return svh;
185 | }
186 |
187 | /**
188 | * Set lower limit temperature value
189 | *
190 | * values: 5-99ºC
191 | *
192 | * default: 5ºC
193 | *
194 | * @return
195 | */
196 | public short getSvl() {
197 | return svl;
198 | }
199 |
200 | /**
201 | * Measure temperature
202 | *
203 | * Measure temperature,check and calibration
204 | *
205 | * 0.1ºC precision Calibration (actual temperature)
206 | *
207 | * @return
208 | */
209 | public double getRoomTempAdjustment() {
210 | return roomTempAdjustment;
211 | }
212 |
213 | /**
214 | * Anti-freezing function
215 | *
216 | * 00:anti-freezing function shut down
217 | *
218 | * 01:anti-freezing function open
219 | *
220 | * 00:anti-freezing function shut down
221 | *
222 | * @return
223 | */
224 | public AntiFreezing getAntiFreezing() {
225 | return antiFreezing;
226 | }
227 |
228 | /**
229 | * Power on memory
230 | *
231 | * 00:Power on no need memory
232 | *
233 | * 01:Power on need memory
234 | *
235 | * default: 00:Power on no need memory
236 | *
237 | * @return
238 | */
239 | public PowerOnMemory getPowerOnMemory() {
240 | return powerOnMemory;
241 | }
242 |
243 | /**
244 | * NOT SURE Factory default
245 | *
246 | * 08:just display,no other meaning
247 | *
248 | * 00:Restore factory default
249 | *
250 | * default: 08
251 | *
252 | * @return
253 | */
254 | public short getFac() {
255 | return fac;
256 | }
257 |
258 | public double getExternalTemp() {
259 | return externalTemp;
260 | }
261 |
262 | @Override
263 | public String toString() {
264 | return "BaseStatusInfo [\nremote lock=" + remoteLock + ",\n power=" + power + ",\n active=" + active
265 | + ",\n manual temp=" + manualTemp + ",\n room temp=" + roomTemp + ",\n thermostat temp="
266 | + thermostatTemp + ",\n auto_mode=" + autoMode + ",\n loop_mode=" + loopMode + ",\n sensor="
267 | + sensorControl + ",\n osv=" + osv + ",\n dif=" + dif + ",\n svh=" + svh + ",\n svl=" + svl
268 | + ",\n room_temp_adj=" + roomTempAdjustment + ",\n anti freeze=" + antiFreezing + ",\n powerOnMemory="
269 | + powerOnMemory + ",\n fac?=" + fac + ",\n external temp=" + externalTemp + "]";
270 | }
271 |
272 | protected static byte boolToByte(boolean v) {
273 | return (byte) (v ? 1 : 0);
274 | }
275 |
276 | protected static boolean byteToBool(byte v) {
277 | return v == (byte) 1;
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/dev/hysen/BaseHysenDevice.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 |
30 | package com.github.mob41.blapi.dev.hysen;
31 |
32 | import java.io.IOException;
33 |
34 | import javax.xml.bind.DatatypeConverter;
35 |
36 | import com.github.mob41.blapi.BLDevice;
37 | import com.github.mob41.blapi.mac.Mac;
38 | import com.github.mob41.blapi.pkt.cmd.hysen.GetBasicInfoCommand;
39 | import com.github.mob41.blapi.pkt.cmd.hysen.GetStatusCommand;
40 | import com.github.mob41.blapi.pkt.cmd.hysen.SetModeCommand;
41 | import com.github.mob41.blapi.pkt.cmd.hysen.SetPeriodsCommand;
42 | import com.github.mob41.blapi.pkt.cmd.hysen.SetPoweCommand;
43 | import com.github.mob41.blapi.pkt.cmd.hysen.SetTempCommand;
44 |
45 | /**
46 | * Base hysen "class" thermostats
47 | *
48 | * Adapted from https://github.com/mjg59/python-broadlink
49 | *
50 | * @author alpapad
51 | *
52 | */
53 | public class BaseHysenDevice extends BLDevice {
54 |
55 | /**
56 | * Generic way to create a BaseHysenDevice
57 | *
58 | * @param deviceType
59 | * Device Type
60 | * @param deviceDesc
61 | * Friendly device description
62 | * @param host
63 | * The target Broadlink hostname
64 | * @param mac
65 | * The target Broadlink MAC address
66 | * @throws IOException
67 | * Problems on constructing socket
68 | */
69 | protected BaseHysenDevice(short deviceType, String deviceDesc, String host, Mac mac) throws IOException {
70 | super(deviceType, deviceDesc, host, mac);
71 | }
72 |
73 | public byte[] decryptFromDeviceMessage(byte[] encData) throws Exception {
74 | return super.decryptFromDeviceMessage(encData);
75 | }
76 |
77 | public double getThermostatTemp() throws Exception {
78 | BaseStatusInfo info = getBasicStatus();
79 | return info.getThermostatTemp();
80 | }
81 |
82 | public double getExternalTemp() throws Exception {
83 | BaseStatusInfo info = getBasicStatus();
84 | return info.getExternalTemp();
85 | }
86 |
87 | public double getRoomTemp() throws Exception {
88 | BaseStatusInfo info = getBasicStatus();
89 | return info.getRoomTemp();
90 | }
91 |
92 | public BaseStatusInfo getBasicStatus() throws Exception {
93 | byte[] pl = new GetBasicInfoCommand().execute(this);
94 | if (pl != null) {
95 | log.debug("getBasicStatus - received bytes: {}", DatatypeConverter.printHexBinary(pl));
96 | return new BaseStatusInfo(pl);
97 | }
98 | return null;
99 | }
100 |
101 | public AdvancedStatusInfo getAdvancedStatus() throws Exception {
102 | byte[] pl = new GetStatusCommand().execute(this);
103 | if (pl != null) {
104 | log.debug("getAdvancedStatus - received bytes: {}", DatatypeConverter.printHexBinary(pl));
105 | return new AdvancedStatusInfo(pl);
106 | }
107 | return null;
108 | }
109 |
110 | /**
111 | * Change controller mode auto_mode = 1 for auto (scheduled/timed) mode, 0 for
112 | * manual mode. Manual mode will activate last used temperature. In typical
113 | * usage call set_temp to activate manual control and set temp. loop_mode refers
114 | * to index in [ "12345,67", "123456,7", "1234567" ] E.g. loop_mode = 0
115 | * ("12345,67") means Saturday and Sunday follow the "weekend" schedule
116 | * loop_mode = 2 ("1234567") means every day (including Saturday and Sunday)
117 | * follows the "weekday" schedule
118 | *
119 | * @throws Exception If I/O goes wrong
120 | */
121 | public void setMode(boolean autoMode, LoopMode loopMode, SensorControl sensorControl) throws Exception {
122 | new SetModeCommand(tob(autoMode), loopMode.getValue(), sensorControl.getValue()).execute(this);
123 | }
124 |
125 | /**
126 | * Change controller mode auto_mode = 1 for auto (scheduled/timed) mode, 0 for
127 | * manual mode. Manual mode will activate last used temperature. In typical
128 | * usage call set_temp to activate manual control and set temp. loop_mode refers
129 | * to index in [ "12345,67", "123456,7", "1234567" ] E.g. loop_mode = 0
130 | * ("12345,67") means Saturday and Sunday follow the "weekend" schedule
131 | * loop_mode = 2 ("1234567") means every day (including Saturday and Sunday)
132 | * follows the "weekday" schedule
133 | *
134 | * @throws Exception If I/O goes wrong
135 | */
136 | public void setMode(boolean autoMode, LoopMode loopMode) throws Exception {
137 | BaseStatusInfo status = this.getBasicStatus();
138 | new SetModeCommand(tob(autoMode), loopMode.getValue(), status.getSensorControl().getValue()).execute(this);
139 | }
140 |
141 | public void setPower(boolean powerOn, boolean remoteLock) throws Exception {
142 | new SetPoweCommand(tob(powerOn), tob(remoteLock)).execute(this);
143 | }
144 |
145 | public void setPower(boolean powerOn) throws Exception {
146 | BaseStatusInfo status = this.getBasicStatus();
147 | new SetPoweCommand(tob(powerOn), tob(status.getRemoteLock())).execute(this);
148 | }
149 |
150 | public void setLock(boolean remoteLock) throws Exception {
151 | BaseStatusInfo status = this.getBasicStatus();
152 | new SetPoweCommand(tob(status.getPower()), tob(remoteLock)).execute(this);
153 | }
154 |
155 | public void setThermostatTemp(double temp) throws Exception {
156 | new SetTempCommand(temp).execute(this);
157 | }
158 |
159 | public void switchToAuto() throws Exception {
160 | BaseStatusInfo status = this.getBasicStatus();
161 | this.setMode(true, status.getLoopMode(), status.getSensorControl());
162 | }
163 |
164 | public void switchToManual() throws Exception {
165 | BaseStatusInfo status = this.getBasicStatus();
166 | this.setMode(false, status.getLoopMode(), status.getSensorControl());
167 | }
168 |
169 | public void setAdvancedOptions(LoopMode loopMode, SensorControl sensor, short osv, short dif, short svh, short svl,
170 | double adj, AntiFreezing antiFreeze, PowerOnMemory poweron) throws Exception {
171 | new SetModeCommand(loopMode.getValue(), sensor.getValue(), tob(osv), tob(dif), tob(svh), tob(svl), adj,
172 | antiFreeze.getValue(), poweron.getValue()).execute(this);
173 | }
174 |
175 | public void setPeriods(Period[] schedule) throws Exception {
176 | new SetPeriodsCommand(schedule).execute(this);
177 | }
178 |
179 | private static byte tob(boolean v) {
180 | return (byte) (v ? 1 : 0);
181 | }
182 |
183 | private static byte tob(short in) {
184 | return (byte) (in & 0xff);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/pkt/dis/DiscoveryPacket.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi.pkt.dis;
30 |
31 | import java.net.InetAddress;
32 | import java.net.UnknownHostException;
33 | import java.util.Calendar;
34 | import java.util.TimeZone;
35 |
36 | import org.slf4j.Logger;
37 | import org.slf4j.LoggerFactory;
38 |
39 | import com.github.mob41.blapi.ex.BLApiRuntimeException;
40 | import com.github.mob41.blapi.pkt.Packet;
41 |
42 | /**
43 | * This class packs a packet to discover Broadlink devices
44 | *
45 | * @author Anthony
46 | *
47 | */
48 | public class DiscoveryPacket implements Packet {
49 |
50 | public static final int DEFAULT_SOURCE_PORT = 0; // This source port is from
51 | // the python-broadlink
52 | // source code
53 |
54 | private static final Logger log = LoggerFactory.getLogger(DiscoveryPacket.class);
55 |
56 | private final byte[] data;
57 |
58 | public DiscoveryPacket() {
59 | this(null);
60 | }
61 |
62 | public DiscoveryPacket(InetAddress localIpAddr) {
63 | this(localIpAddr, DEFAULT_SOURCE_PORT, Calendar.getInstance(), TimeZone.getDefault());
64 | }
65 |
66 | public DiscoveryPacket(InetAddress localIpAddr, int sourcePort) {
67 | this(localIpAddr, sourcePort, Calendar.getInstance(), TimeZone.getDefault());
68 | }
69 |
70 | public DiscoveryPacket(InetAddress localIpAddr, int sourcePort, Calendar cal, TimeZone tz) {
71 | log.debug("DiscoveryPacket constructor start");
72 | log.debug("cal=" + cal.getTimeInMillis() + " tz=" + tz.getID());
73 | if (localIpAddr == null) {
74 | log.debug("localIpAddr is null. Calling InetAddress.getLocalHost");
75 | try {
76 | localIpAddr = InetAddress.getLocalHost();
77 | } catch (UnknownHostException e) {
78 | log.error("Could not relieve local IP address", e);
79 | throw new BLApiRuntimeException("Could not relieve local IP address", e);
80 | }
81 | }
82 | log.debug("localIpAddr= " + localIpAddr.getHostName() + "/" + localIpAddr.getHostAddress());
83 |
84 | int rawOffset = tz.getRawOffset();
85 | int tzOffset = rawOffset / 3600;
86 |
87 | log.debug("Raw offset: " + rawOffset);
88 | log.debug("Calculated offset: getRawOffset/1000/-3600=" + tzOffset);
89 |
90 | int min = cal.get(Calendar.MINUTE);
91 | int hr = cal.get(Calendar.HOUR);
92 |
93 | int year = cal.get(Calendar.YEAR);
94 | int dayOfWk = dayOfWeekConv(cal.get(Calendar.DAY_OF_WEEK)); // Day of
95 | // week (May
96 | // return -1
97 | // if
98 | // Calendar
99 | // return a
100 | // wrong
101 | // field
102 | // value)
103 | int dayOfMn = cal.get(Calendar.DAY_OF_MONTH); // Day of month
104 | int month = cal.get(Calendar.MONTH) + 1; // Month
105 |
106 | log.debug("min=" + min + " hr=" + hr);
107 | log.debug("year=" + year + " dayOfWk=" + dayOfWk);
108 | log.debug("dayOfMn=" + dayOfMn + " month=" + month);
109 |
110 | byte[] ipAddrBytes = localIpAddr.getAddress();
111 |
112 | data = new byte[0x30]; // 48-byte
113 |
114 | // data[0x00-0x07] = 0x00;
115 |
116 | // This is directly "copied" from the python-broadlink source code
117 | if (tzOffset < 0) {
118 | data[0x08] = (byte) (0xff + tzOffset - 1);
119 | data[0x09] = (byte) 0xff;
120 | data[0x0a] = (byte) 0xff;
121 | data[0x0b] = (byte) 0xff;
122 | log.debug("tzOffset<0: 0x08=" + Integer.toHexString(0xff + tzOffset - 1) + " 0x09-0x0b=0xff");
123 | } else {
124 | data[0x08] = (byte) tzOffset;
125 | data[0x09] = (byte) 0x00;
126 | data[0x0a] = (byte) 0x00;
127 | data[0x0b] = (byte) 0x00;
128 | log.debug("tzOffset>0: 0x08=" + Integer.toHexString(tzOffset) + " 0x09-0x0b=0x00");
129 | }
130 |
131 | data[0x0c] = (byte) (year & 0xff);
132 | data[0x0d] = (byte) (year >> 8); // Shift 8 bits
133 |
134 | data[0x0e] = (byte) min;
135 | data[0x0f] = (byte) hr;
136 |
137 | // subyear = str(year)[2:] //Somehow this code is dirty to do the same
138 | // as python code
139 | data[0x10] = (byte) Integer.parseInt(Integer.toString(year).substring(2, 4)); // Year
140 | // without
141 | // century
142 |
143 | data[0x11] = (byte) dayOfWk;
144 | data[0x12] = (byte) dayOfMn;
145 | data[0x13] = (byte) month;
146 |
147 | // IP address
148 | data[0x18] = ipAddrBytes[0];
149 | data[0x19] = ipAddrBytes[1];
150 | data[0x1a] = ipAddrBytes[2];
151 | data[0x1b] = ipAddrBytes[3];
152 |
153 | data[0x1c] = (byte) (sourcePort & 0xff);
154 | data[0x1d] = (byte) (sourcePort >> 8);
155 |
156 | data[0x26] = 6;
157 |
158 | // Checksum
159 | short checksum = (short) 0xbeaf;
160 |
161 | for (int i = 0; i < data.length; i++) {
162 | checksum += (int) (data[i] & 0xff);
163 | }
164 |
165 | log.debug("checksum=" + Integer.toHexString(checksum));
166 |
167 | data[0x20] = (byte) (checksum & 0xff);
168 | data[0x21] = (byte) (checksum >> 8);
169 |
170 | log.debug("DiscoveryPacket constructor end");
171 | }
172 |
173 | @Override
174 | public byte[] getData() {
175 | return data;
176 | }
177 |
178 | private static int dayOfWeekConv(int fieldVal) {
179 | switch (fieldVal) {
180 | case Calendar.SUNDAY:
181 | return 6;
182 | case Calendar.MONDAY:
183 | return 0;
184 | case Calendar.TUESDAY:
185 | return 1;
186 | case Calendar.WEDNESDAY:
187 | return 2;
188 | case Calendar.THURSDAY:
189 | return 3;
190 | case Calendar.FRIDAY:
191 | return 4;
192 | case Calendar.SATURDAY:
193 | return 5;
194 | }
195 | return -1;
196 | }
197 |
198 | }
199 |
--------------------------------------------------------------------------------
/src/main/java/com/github/mob41/blapi/BLDevice.java:
--------------------------------------------------------------------------------
1 | /*******************************************************************************
2 | * MIT License
3 | *
4 | * Copyright (c) 2016, 2017 Anthony Law
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | *
24 | * Contributors:
25 | * - Anthony Law (mob41) - Initial API Implementation
26 | * - bwssytems
27 | * - Christian Fischer (computerlyrik)
28 | *******************************************************************************/
29 | package com.github.mob41.blapi;
30 |
31 | import java.io.Closeable;
32 | import java.io.IOException;
33 | import java.net.DatagramPacket;
34 | import java.net.DatagramSocket;
35 | import java.net.InetAddress;
36 | import java.net.SocketTimeoutException;
37 | import java.util.ArrayList;
38 | import java.util.List;
39 | import java.util.Random;
40 |
41 | import javax.xml.bind.DatatypeConverter;
42 |
43 | import org.slf4j.Logger;
44 | import org.slf4j.LoggerFactory;
45 |
46 | import com.github.mob41.blapi.mac.Mac;
47 | import com.github.mob41.blapi.pkt.CmdPacket;
48 | import com.github.mob41.blapi.pkt.CmdPayload;
49 | import com.github.mob41.blapi.pkt.Packet;
50 | import com.github.mob41.blapi.pkt.auth.AES;
51 | import com.github.mob41.blapi.pkt.auth.AuthCmdPayload;
52 | import com.github.mob41.blapi.pkt.dis.DiscoveryPacket;
53 |
54 | /**
55 | * This is the base class of all Broadlink devices (e.g. SP1, RMPro)
56 | *
57 | * @author Anthony
58 | *
59 | */
60 | public abstract class BLDevice implements Closeable {
61 |
62 | /**
63 | * The specific logger for this class
64 | */
65 | protected static final Logger log = LoggerFactory.getLogger(BLDevice.class);
66 |
67 | /**
68 | * Initial key for encryption
69 | */
70 | public static final byte[] INITIAL_KEY = { 0x09, 0x76, 0x28, 0x34, 0x3f, (byte) 0xe9, (byte) 0x9e, 0x23, 0x76, 0x5c,
71 | 0x15, 0x13, (byte) 0xac, (byte) 0xcf, (byte) 0x8b, 0x02 }; // 16-byte
72 |
73 | /**
74 | * Initial iv for encryption
75 | */
76 | public static final byte[] INITIAL_IV = { 0x56, 0x2e, 0x17, (byte) 0x99, 0x6d, 0x09, 0x3d, 0x28, (byte) 0xdd,
77 | (byte) 0xb3, (byte) 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58 }; // 16-short
78 |
79 | public static final int DEFAULT_BYTES_SIZE = 0x38; // 56-bytes
80 |
81 | // Devices type HEX
82 |
83 | public static final short DEV_SP1 = 0x0;
84 |
85 | public static final short DEV_SP2 = 0x2711;
86 |
87 | public static final short DEV_SP2_HONEYWELL_ALT1 = 0x2719;
88 |
89 | public static final short DEV_SP2_HONEYWELL_ALT2 = 0x7919;
90 |
91 | public static final short DEV_SP2_HONEYWELL_ALT3 = 0x271a;
92 |
93 | public static final short DEV_SP2_HONEYWELL_ALT4 = 0x791a;
94 |
95 | public static final short DEV_SPMINI = 0x2720;
96 |
97 | public static final short DEV_SP3 = 0x753e;
98 |
99 | public static final short DEV_SPMINI2 = 0x2728;
100 |
101 | public static final short DEV_SPMINI_OEM_ALT1 = 0x2733;
102 |
103 | public static final short DEV_SPMINI_OEM_ALT2 = 0x273e;
104 |
105 | public static final short DEV_SPMINI_PLUS = 0x2736;
106 |
107 | public static final short DEV_RM_2 = 0x2712;
108 |
109 | public static final short DEV_RM_MINI = 0x2737;
110 |
111 | public static final short DEV_RM_MINI_3 = 0x27c2;
112 |
113 | public static final short DEV_RM_PRO_PHICOMM = 0x273d;
114 |
115 | public static final short DEV_RM_2_HOME_PLUS = 0x2783;
116 |
117 | public static final short DEV_RM_2_2HOME_PLUS_GDT = 0x277c;
118 |
119 | public static final short DEV_RM_2_PRO_PLUS = 0x272a;
120 |
121 | public static final short DEV_RM_2_PRO_PLUS_2 = 0x2787;
122 |
123 | public static final short DEV_RM_2_PRO_PLUS_2_BL = 0x278b;
124 |
125 | public static final short DEV_RM_MINI_SHATE = 0x278f;
126 |
127 | public static final short DEV_A1 = 0x2714;
128 |
129 | public static final short DEV_MP1 = 0x4EB5;
130 |
131 | public static final short DEV_HYSEN = 0x4EAD;
132 |
133 | public static final short DEV_FLOUREON = 0xffffffad;
134 |
135 | //
136 | // Friendly device description
137 | //
138 | // Notice: Developers are not recommended to use device description as device identifiers.
139 | // Instead, developers are advised to use Device Type Hex numbers.
140 |
141 | //Unknown
142 |
143 | public static final String DESC_UNKNOWN = "Unknown Device";
144 |
145 | //RM Series
146 |
147 | public static final String DESC_RM_2 = "RM 2";
148 |
149 | public static final String DESC_RM_MINI = "RM Mini";
150 |
151 | public static final String DESC_RM_MINI_3 = "RM Mini 3";
152 |
153 | public static final String DESC_RM_PRO_PHICOMM = "RM Pro";
154 |
155 | public static final String DESC_RM_2_HOME_PLUS = "RM 2 Home Plus";
156 |
157 | public static final String DESC_RM_2_2HOME_PLUS_GDT = "RM 2 Home Plus GDT";
158 |
159 | public static final String DESC_RM_2_PRO_PLUS = "RM 2 Pro Plus";
160 |
161 | public static final String DESC_RM_2_PRO_PLUS_2 = "RM 2 Pro Plus 2";
162 |
163 | public static final String DESC_RM_2_PRO_PLUS_2_BL = "RM 2 Pro Plus 2 BL";
164 |
165 | public static final String DESC_RM_MINI_SHATE = "RM Mini SHATE";
166 |
167 | //A Series
168 |
169 | public static final String DESC_A1 = "Environmental Sensor";
170 |
171 | //MP Series
172 |
173 | public static final String DESC_MP1 = "Power Strip";
174 |
175 | //SP Series
176 |
177 | public static final String DESC_SP1 = "Smart Plug V1";
178 |
179 | public static final String DESC_SP2 = "Smart Plug V2";
180 |
181 | public static final String DESC_SP2_HONEYWELL_ALT1 = "Smart Plug Honeywell Alt 1";
182 |
183 | public static final String DESC_SP2_HONEYWELL_ALT2 = "Smart Plug Honeywell Alt 2";
184 |
185 | public static final String DESC_SP2_HONEYWELL_ALT3 = "Smart Plug Honeywell Alt 3";
186 |
187 | public static final String DESC_SP2_HONEYWELL_ALT4 = "Smart Plug Honeywell Alt 4";
188 |
189 | public static final String DESC_SPMINI = "Smart Plug Mini";
190 |
191 | public static final String DESC_SP3 = "Smart Plug V3";
192 |
193 | public static final String DESC_SPMINI2 = "Smart Plug Mini V2";
194 |
195 | public static final String DESC_SPMINI_OEM_ALT1 = "Smart Plug OEM Alt 1";
196 |
197 | public static final String DESC_SPMINI_OEM_ALT2 = "Smart Plug OEM Alt 2";
198 |
199 | public static final String DESC_SPMINI_PLUS = "Smart Plug Mini Plus";
200 |
201 | public static final String DESC_HYSEN = "Hysen Thermostat";
202 |
203 | public static final String DESC_FLOUREON = "Floureon Thermostat";
204 | /**
205 | * The destination port for discovery broadcasting (from __init__.py)
206 | */
207 | public static final int DISCOVERY_DEST_PORT = 80;
208 |
209 | /**
210 | * The discovery receive buffer size (from __init__.py)
211 | */
212 | public static final int DISCOVERY_RECEIVE_BUFFER_SIZE = 0x40; // 64-bytes
213 |
214 | /**
215 | * Default discovery timeout (10 seconds)
216 | */
217 | public static final int DEFAULT_TIMEOUT = 10000; // 10 seconds (10000 ms)
218 |
219 | /**
220 | * Packet count that is sent by this instance of BLDevice. This is for
221 | * {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
222 | */
223 | private int pktCount;
224 |
225 | /**
226 | * Encryption key. Initialization value is {@link #INITIAL_KEY INITIAL_KEY}.
227 | * This is for {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
228 | */
229 | private byte[] key;
230 |
231 | /**
232 | * Encryption iv. Initialization value is {@link #INITIAL_IV INITIAL_IV}.
233 | * This is for {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
234 | */
235 | private byte[] iv;
236 |
237 | /**
238 | * Device/Client ID. Initialization value is {0,0,0,0}. And it
239 | * is changed after the {@link #auth() auth} method, that Broadlink devices
240 | * will provide a id for this client/device. This is for
241 | * {@link #sendCmdPkt(CmdPayload) sendCmdPkt} method.
242 | */
243 | private byte[] id;
244 |
245 | /**
246 | * Device type received from discovering devices, or those
247 | * BLDevice.DEV_* constants
248 | */
249 | private final short deviceType;
250 |
251 | /**
252 | * A friendly description of this device
253 | */
254 | private final String deviceDesc;
255 |
256 | /**
257 | * Specific datagram socket for this instance, to reuse address.
258 | */
259 | private DatagramSocket sock;
260 |
261 | /**
262 | * Target device host
263 | */
264 | private String host;
265 |
266 | /**
267 | * Target device MAC, using {@link com.github.mob41.blapi.mac.Mac}
268 | * implementation to handle MAC addresses
269 | */
270 | private Mac mac;
271 |
272 | /**
273 | * AES decryption object
274 | */
275 | private AES aes = null;
276 |
277 | /**
278 | * flag to denote this object alreay authorized.
279 | */
280 | private boolean alreadyAuthorized;
281 |
282 | /**
283 | * Constructs a BLDevice, with a device type (constants),
284 | * hostname and MAC address
285 | *
286 | * @param deviceType
287 | * Device type constants (BLDevice.DEV_*)
288 | * @param deviceDesc
289 | * Friendly device description
290 | * @param host
291 | * Hostname of target Broadlink device
292 | * @param mac
293 | * MAC address of target Broadlink device
294 | * @throws IOException
295 | * Problems on constructing a datagram socket
296 | */
297 | protected BLDevice(short deviceType, String deviceDesc, String host, Mac mac) throws IOException {
298 | key = INITIAL_KEY;
299 | iv = INITIAL_IV;
300 | id = new byte[] { 0, 0, 0, 0 };
301 |
302 | pktCount = new Random().nextInt(0xffff);
303 |
304 | this.deviceType = deviceType;
305 | this.deviceDesc = deviceDesc;
306 |
307 | this.host = host;
308 | this.mac = mac;
309 |
310 | sock = new DatagramSocket();
311 | sock.setReuseAddress(true);
312 | sock.setBroadcast(true);
313 | aes = new AES(iv, key);
314 | alreadyAuthorized = false;
315 | }
316 |
317 | /**
318 | * Releases the resources of this BLDevice
319 | */
320 | @Override
321 | public void close() {
322 | sock.close();
323 | }
324 |
325 | /**
326 | * Returns the device type of this Broadlink device
327 | *
328 | * @return The device type in short
329 | */
330 | public short getDeviceType() {
331 | return deviceType;
332 | }
333 |
334 | /**
335 | * Returns this Broadlink device's hostname / IP address
336 | *
337 | * @return The hostname / IP address in String
338 | */
339 | public String getHost() {
340 | return host;
341 | }
342 |
343 | /**
344 | * Returns this Broadlink device's MAC address
345 | *
346 | * @return The MAC address in BLApi's Mac implementation
347 | */
348 | public Mac getMac() {
349 | return mac;
350 | }
351 |
352 | public AES getAes() {
353 | return aes;
354 | }
355 |
356 | /**
357 | * Returns a friendly description of this BLDevice
358 | * @return a String
359 | */
360 | public String getDeviceDescription() {
361 | return deviceDesc;
362 | }
363 |
364 | /**
365 | * Compatibility with previous code
366 | * @return Boolean whether this method is success or not
367 | * @throws IOException If I/O goes wrong
368 | */
369 | public boolean auth() throws IOException {
370 | return auth(false);
371 | }
372 |
373 | /**
374 | * Authenticates with the broadlink device, before any other control
375 | * commands
376 | * @param reauth Setting this to true forces to perform re-auth with the device. Defaults not to perform re-auth.
377 | * @return Boolean whether this method is success or not
378 | * @throws IOException
379 | * If I/O goes wrong
380 | */
381 | public boolean auth(boolean reauth) throws IOException {
382 | log.debug("auth Authentication method starts");
383 | if(alreadyAuthorized && !reauth) {
384 | log.debug("auth Already Authorized.");
385 | return true;
386 | }
387 |
388 | AuthCmdPayload sendPayload = new AuthCmdPayload();
389 | log.debug("auth Sending CmdPacket with AuthCmdPayload: cmd=" + Integer.toHexString(sendPayload.getCommand())
390 | + " len=" + sendPayload.getPayload().getData().length);
391 |
392 | log.debug("auth AuthPayload initial bytes to send: {}", DatatypeConverter.printHexBinary(sendPayload.getPayload().getData()));
393 |
394 | DatagramPacket recvPack = sendCmdPkt(10000, 2048, sendPayload);
395 |
396 | byte[] data = recvPack.getData();
397 |
398 | if(data.length <= 0) {
399 | log.error("auth Received 0 bytes on initial request.");
400 | alreadyAuthorized = false;
401 | return false;
402 | }
403 |
404 | log.debug("auth recv encrypted data bytes (" + data.length +") after initial req: {}", DatatypeConverter.printHexBinary(data));
405 |
406 | byte[] payload = null;
407 | try {
408 | log.debug("auth Decrypting encrypted data");
409 |
410 | payload = decryptFromDeviceMessage(data);
411 |
412 | log.debug("auth Decrypted. len=" + payload.length);
413 |
414 | } catch (Exception e) {
415 | log.error("auth Received datagram decryption error. Aborting method", e);
416 | alreadyAuthorized = false;
417 | return false;
418 | }
419 |
420 | log.debug("auth Packet received payload bytes: " + DatatypeConverter.printHexBinary(payload));
421 |
422 | key = subbytes(payload, 0x04, 0x14);
423 |
424 | log.debug("auth Packet received key bytes: " + DatatypeConverter.printHexBinary(key));
425 |
426 | if (key.length % 16 != 0) {
427 | log.error("auth Received key len is not a multiple of 16! Aborting");
428 | alreadyAuthorized = false;
429 | return false;
430 | }
431 |
432 | // recreate AES object with new key
433 | aes = new AES(iv, key);
434 |
435 | id = subbytes(payload, 0x00, 0x04);
436 |
437 | log.debug("auth Packet received id bytes: " + DatatypeConverter.printHexBinary(id) + " with ID len=" + id.length);
438 |
439 | log.debug("auth End of authentication method");
440 | alreadyAuthorized = true;
441 |
442 | return true;
443 | }
444 |
445 | /**
446 | * Sends a command packet from localhost to Broadlink device, with buffer
447 | * size 1024 bytes, 10 seconds timeout
448 | *
449 | * Before any commands to be sent to the device, {@link #auth() auth} must
450 | * be ran first in order to authenticate with the device and gain a device
451 | * ID, encryption key and IV.
452 | *
453 | * @param cmdPayload
454 | * Command data to be sent
455 | * @return {@link DatagramPacket} containing the byte data and sender host
456 | * information.
457 | * @throws IOException
458 | * Problems when sending the packet
459 | */
460 | public DatagramPacket sendCmdPkt(CmdPayload cmdPayload) throws IOException {
461 | return sendCmdPkt(10000, cmdPayload);
462 | }
463 |
464 | /**
465 | * Sends a command packet from localhost to Broadlink device, with default
466 | * buffer size 1024 bytes
467 | *
468 | * Before any commands to be sent to the device, {@link #auth() auth} must
469 | * be ran first in order to authenticate with the device and gain a device
470 | * ID, encryption key and IV.
471 | *
472 | * @param timeout
473 | * Socket read timeout
474 | * @param cmdPayload
475 | * Command data to be sent
476 | * @return {@link DatagramPacket} containing the byte data and sender host
477 | * information.
478 | * @throws IOException
479 | * Problems when sending the packet
480 | */
481 | public DatagramPacket sendCmdPkt(int timeout, CmdPayload cmdPayload) throws IOException {
482 | return sendCmdPkt(InetAddress.getLocalHost(), 0, timeout, 1024, cmdPayload);
483 | }
484 |
485 | /**
486 | * Sends a command packet from localhost to Broadlink device
487 | *
488 | * Before any commands to be sent to the device, {@link #auth() auth} must
489 | * be ran first in order to authenticate with the device and gain a device
490 | * ID, encryption key and IV.
491 | *
492 | * @param timeout
493 | * Socket read timeout
494 | * @param bufSize
495 | * Receive datagram buffer size
496 | * @param cmdPayload
497 | * Command data to be sent
498 | * @return {@link DatagramPacket} containing the byte data and sender host
499 | * information.
500 | * @throws IOException
501 | * Problems when sending the packet
502 | */
503 | public DatagramPacket sendCmdPkt(int timeout, int bufSize, CmdPayload cmdPayload) throws IOException {
504 | return sendCmdPkt(InetAddress.getLocalHost(), 0, timeout, bufSize, cmdPayload);
505 | }
506 |
507 | /**
508 | * Binds to a specific IP address and sends a command packet to Broadlink
509 | * device
510 | *
511 | * Before any commands to be sent to the device, {@link #auth() auth} must
512 | * be ran first in order to authenticate with the device and gain a device
513 | * ID, encryption key and IV.
514 | *
515 | * @param sourceIpAddr
516 | * Bind the socket to this IP address
517 | * @param sourcePort
518 | * Bind the socket to this port
519 | * @param timeout
520 | * Socket read timeout
521 | * @param bufSize
522 | * Receive datagram buffer size
523 | * @param cmdPayload
524 | * Command data to be sent
525 | * @return {@link DatagramPacket} containing the byte data and sender host
526 | * information.
527 | * @throws IOException
528 | * Problems when sending the packet
529 | */
530 | public DatagramPacket sendCmdPkt(InetAddress sourceIpAddr, int sourcePort, int timeout, int bufSize,
531 | CmdPayload cmdPayload) throws IOException {
532 | CmdPacket cmdPkt = new CmdPacket(mac, pktCount++, id, aes, cmdPayload);
533 | log.debug("sendCmdPkt - Send Command Packet bytes: {}", DatatypeConverter.printHexBinary(cmdPkt.getData()));
534 | return sendPkt(sock, cmdPkt, InetAddress.getByName(host), 80, timeout, bufSize);
535 | }
536 |
537 | /**
538 | * Creates a Broadlink device client
539 | *
540 | * @param deviceType
541 | * Device type constant (BLDevice.DEV_*)
542 | * @param host
543 | * Target Broadlink device hostname
544 | * @param mac
545 | * Target Broadlink device MAC address
546 | * @return A BLDevice client
547 | * @throws IOException
548 | * Problems when constucting a datagram socket
549 | */
550 | public static BLDevice createInstance(short deviceType, String host, Mac mac) throws IOException {
551 | String desc = BLDevice.getDescOfType(deviceType);
552 | switch (deviceType) {
553 | case DEV_SP1:
554 | return new SP1Device(host, mac);
555 | case DEV_SP2:
556 | case DEV_SP2_HONEYWELL_ALT1:
557 | case DEV_SP2_HONEYWELL_ALT2:
558 | case DEV_SP2_HONEYWELL_ALT3:
559 | case DEV_SP2_HONEYWELL_ALT4:
560 | case DEV_SPMINI:
561 | case DEV_SP3:
562 | case DEV_SPMINI2:
563 | case DEV_SPMINI_OEM_ALT1:
564 | case DEV_SPMINI_OEM_ALT2:
565 | case DEV_SPMINI_PLUS:
566 | return new SP2Device(deviceType, desc, host, mac);
567 | case DEV_RM_2:
568 | case DEV_RM_MINI:
569 | case DEV_RM_MINI_3:
570 | return new RM2Device(deviceType, desc, host, mac);
571 | case DEV_RM_PRO_PHICOMM:
572 | case DEV_RM_2_HOME_PLUS:
573 | case DEV_RM_2_2HOME_PLUS_GDT:
574 | case DEV_RM_2_PRO_PLUS:
575 | case DEV_RM_2_PRO_PLUS_2:
576 | case DEV_RM_2_PRO_PLUS_2_BL:
577 | case DEV_RM_MINI_SHATE:
578 | return new RM2Device(deviceType, desc, host, mac);
579 | case DEV_A1:
580 | return new A1Device(host, mac);
581 | case DEV_MP1:
582 | return new MP1Device(host, mac);
583 | case DEV_FLOUREON:
584 | return new FloureonDevice(host, mac);
585 | case DEV_HYSEN:
586 | return new HysenDevice(host, mac);
587 | }
588 | return null;
589 | }
590 |
591 | /**
592 | * Discover Broadlink devices in the local network, with
593 | * {@link #DEFAULT_TIMEOUT default timeout}
594 | *
595 | * @return An array of BLDevice in the network
596 | * @throws IOException
597 | * Problems when discovering
598 | */
599 | public static BLDevice[] discoverDevices() throws IOException {
600 | return discoverDevices(DEFAULT_TIMEOUT);
601 | }
602 |
603 | /**
604 | * Discover Broadlink devices in the local network
605 | *
606 | * @param timeout
607 | * Socket read timeout
608 | * @return An array of BLDevice in the network
609 | * @throws IOException
610 | * Problems when discovering
611 | */
612 | public static BLDevice[] discoverDevices(int timeout) throws IOException {
613 | return discoverDevices(InetAddress.getLocalHost(), 0, timeout);
614 | }
615 |
616 | /**
617 | * Discover Broadlink devices in the network, binded with a specific IP
618 | * address
619 | *
620 | * @param sourceIpAddr
621 | * The IP address to be binded
622 | * @param sourcePort
623 | * The port to be binded
624 | * @param timeout
625 | * Socket read timeout
626 | * @return An array of BLDevice in the network
627 | * @throws IOException
628 | * Problems when discovering
629 | */
630 | public static BLDevice[] discoverDevices(InetAddress sourceIpAddr, int sourcePort, int timeout) throws IOException {
631 | boolean debug = log.isDebugEnabled();
632 |
633 | if (debug)
634 | log.debug("Discovering devices");
635 |
636 | Listend - start)
941 | */
942 | public static byte[] subbytes(byte[] data, int start, int end) {
943 | byte[] out = new byte[end - start];
944 |
945 | int outi = 0;
946 | for (int i = start; i < end; i++, outi++) {
947 | out[outi] = data[i];
948 | }
949 |
950 | return out;
951 | }
952 |
953 | /**
954 | * Sends a compiled packet to a destination host and port, and receives a
955 | * datagram from the source port specified.
956 | *
957 | * @param pkt
958 | * The compiled packet to be sent
959 | * @param sourceIpAddr
960 | * Source IP address to be binded for receiving datagrams
961 | * @param sourcePort
962 | * Source Port to be bineded for receiving datagrams
963 | * @param destIpAddr
964 | * Destination IP address
965 | * @param destPort
966 | * Destination Port
967 | * @param timeout
968 | * Socket timeout. 0 will disable the timeout
969 | * @param bufSize
970 | * Receiving datagram's buffer size
971 | * @return The received datagram
972 | * @throws IOException
973 | * Thrown if socket timed out, cannot bind source IP and source
974 | * port, no permission, etc.
975 | */
976 | public static DatagramPacket sendPkt(Packet pkt, InetAddress sourceIpAddr, int sourcePort, InetAddress destIpAddr,
977 | int destPort, int timeout, int bufSize) throws IOException {
978 | log.debug("sendPkt - call with create socket for: " + sourceIpAddr.getHostAddress() + " and port " + sourcePort);
979 | DatagramSocket sock = new DatagramSocket(sourcePort, sourceIpAddr);
980 |
981 | sock.setBroadcast(true);
982 | sock.setReuseAddress(true);
983 |
984 | DatagramPacket recePkt = sendPkt(sock, pkt, destIpAddr, destPort, timeout, bufSize);
985 | sock.close();
986 |
987 | return recePkt;
988 | }
989 |
990 | /**
991 | * Sends a compiled packet to a destination host and port, and receives a
992 | * datagram from the source port specified.
993 | *
994 | * @param sock
995 | * Uses an external socket
996 | * @param pkt
997 | * The compiled packet to be sent
998 | * @param destIpAddr
999 | * Destination IP address
1000 | * @param destPort
1001 | * Destination Port
1002 | * @param timeout
1003 | * Socket timeout. 0 will disable the timeout
1004 | * @param bufSize
1005 | * Receiving datagram's buffer size
1006 | * @return The received datagram
1007 | * @throws IOException
1008 | * Thrown if socket timed out, cannot bind source IP and source
1009 | * port, no permission, etc.
1010 | */
1011 | public static DatagramPacket sendPkt(DatagramSocket sock, Packet pkt, InetAddress destIpAddr, int destPort, int timeout, int bufSize) throws IOException {
1012 |
1013 | String boundHost = null;
1014 | if(sock.getInetAddress() == null)
1015 | boundHost = "0.0.0.0";
1016 | else
1017 | boundHost = sock.getInetAddress().getHostAddress();
1018 | log.debug("sendPkt - call with given sock for " + boundHost + " and port " + sock.getPort());
1019 |
1020 | byte[] data = pkt.getData();
1021 | DatagramPacket sendpack = new DatagramPacket(data, data.length, destIpAddr, destPort);
1022 | log.debug("snedPkt - data for length: " + data.length + " to: " + sendpack.getAddress().getHostAddress() + " for port: " + sendpack.getPort());
1023 |
1024 | byte[] rece = new byte[bufSize];
1025 | DatagramPacket recepack = new DatagramPacket(rece, 0, rece.length);
1026 |
1027 | long startTime = System.currentTimeMillis();
1028 | long elapsed;
1029 | while ((elapsed = System.currentTimeMillis() - startTime) < timeout) {
1030 | try {
1031 | sock.send(sendpack);
1032 | sock.setSoTimeout(1000);
1033 | sock.receive(recepack);
1034 | break;
1035 | } catch (SocketTimeoutException e) {
1036 | if (elapsed > timeout) {
1037 | break;
1038 | }
1039 |
1040 | continue;
1041 | }
1042 | }
1043 |
1044 | log.debug("sendPkt - recv data bytes (" + recepack.getData().length +") after initial req: {}", DatatypeConverter.printHexBinary(recepack.getData()));
1045 | recepack.setData(removeNullsFromEnd(recepack.getData(), 0));
1046 | return recepack;
1047 | }
1048 |
1049 | public static byte[] chgLen(byte[] data, int newLen) {
1050 | byte[] newBytes = new byte[newLen];
1051 | for (int i = 0; i < data.length; i++) {
1052 | newBytes[i] = data[i];
1053 | }
1054 | return newBytes;
1055 | }
1056 | }
1057 |
--------------------------------------------------------------------------------