Skip to content

Commit

Permalink
set sleep schedules
Browse files Browse the repository at this point in the history
  • Loading branch information
jwoglom committed Mar 31, 2024
1 parent 3d1a7a9 commit b38a798
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@
import com.jwoglom.pumpx2.pump.messages.response.currentStatus.UnknownMobiOpcodeNeg124Response;
import com.jwoglom.pumpx2.pump.messages.request.control.SetModesRequest;
import com.jwoglom.pumpx2.pump.messages.response.control.SetModesResponse;
import com.jwoglom.pumpx2.pump.messages.request.control.SetSleepScheduleRequest;
import com.jwoglom.pumpx2.pump.messages.response.control.SetSleepScheduleResponse;
// IMPORT_END
import com.jwoglom.pumpx2.shared.L;

Expand Down Expand Up @@ -226,6 +228,7 @@ public enum Messages {
UNKNOWN_MOBI_OPCODE_NEG66(UnknownMobiOpcodeNeg66Request.class, UnknownMobiOpcodeNeg66Response.class),
UNKNOWN_MOBI_OPCODE_NEG124(UnknownMobiOpcodeNeg124Request.class, UnknownMobiOpcodeNeg124Response.class),
SET_MODES(SetModesRequest.class, SetModesResponse.class),
SET_SLEEP_SCHEDULE(SetSleepScheduleRequest.class, SetSleepScheduleResponse.class),
// MESSAGES_END
;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.jwoglom.pumpx2.pump.messages.models;

import java.util.Objects;

/**
* Used by ControlIQSleepScheduleResponse and RemindersResponse.Reminder
*/
Expand Down Expand Up @@ -28,4 +30,17 @@ public int min() {
public int encode() {
return hour*60 + min;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MinsTime minsTime = (MinsTime) o;
return encode() == minsTime.encode();
}

@Override
public int hashCode() {
return Objects.hash(hour, min);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public enum MultiDay {
SUNDAY(64),
;

public static final Set<MultiDay> ALL_DAYS = fromBitmask(127);

private final int id;

MultiDay(int id) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.jwoglom.pumpx2.pump.messages.request.control;

import com.google.common.base.Preconditions;
import com.jwoglom.pumpx2.pump.messages.bluetooth.Characteristic;
import com.jwoglom.pumpx2.pump.messages.helpers.Bytes;
import com.jwoglom.pumpx2.pump.messages.Message;
import com.jwoglom.pumpx2.pump.messages.MessageType;
import com.jwoglom.pumpx2.pump.messages.annotations.MessageProps;
import com.jwoglom.pumpx2.pump.messages.models.KnownApiVersion;
import com.jwoglom.pumpx2.pump.messages.models.SupportedDevices;
import com.jwoglom.pumpx2.pump.messages.response.control.SetSleepScheduleResponse;
import com.jwoglom.pumpx2.pump.messages.response.currentStatus.ControlIQSleepScheduleResponse;

@MessageProps(
opCode=-50,
size=8,
type=MessageType.REQUEST,
characteristic=Characteristic.CONTROL,
signed=true,
minApi=KnownApiVersion.MOBI_API_V3_5,
supportedDevices=SupportedDevices.MOBI_ONLY,
response=SetSleepScheduleResponse.class
)
public class SetSleepScheduleRequest extends Message {

private int slot;
private byte[] rawSchedule;
private int flag;

public SetSleepScheduleRequest() {}

public SetSleepScheduleRequest(int slot, byte[] rawSchedule, int flag) {
this.cargo = buildCargo(slot, rawSchedule, flag);
}

public SetSleepScheduleRequest(int slot, ControlIQSleepScheduleResponse.SleepSchedule schedule, int flag) {
this.cargo = buildCargo(slot, schedule.build(), flag);
}

public SetSleepScheduleRequest(byte[] raw) {
parse(raw);

}

public void parse(byte[] raw) {
raw = this.removeSignedRequestHmacBytes(raw);
Preconditions.checkArgument(raw.length == props().size());
this.cargo = raw;
this.slot = raw[0];
this.rawSchedule = Bytes.dropLastN(Bytes.dropFirstN(raw, 1), 1);
this.flag = raw[7];

}


public static byte[] buildCargo(int slot, byte[] schedule, int flag) {
return Bytes.combine(
new byte[]{ (byte) slot },
schedule,
new byte[]{ (byte) flag }
);
}

public int getSlot() {
return slot;
}

public byte[] getRawSchedule() {
return rawSchedule;
}

public ControlIQSleepScheduleResponse.SleepSchedule getSleepSchedule() {
return new ControlIQSleepScheduleResponse.SleepSchedule(rawSchedule, 0);
}

public int getFlag() {
return flag;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import com.jwoglom.pumpx2.pump.messages.models.SupportedDevices;
opCode={{requestOpcode}},
size={{requestSize}},
type=MessageType.REQUEST,
characteristic=Characteristic.{{catUuid}},
characteristic=Characteristic.{{catUuid}},{% if catUuid == "CONTROL" %}
signed=true,{% endif %}
{% if mobiOnly %}minApi=KnownApiVersion.MOBI_API_V3_5,
supportedDevices=SupportedDevices.MOBI_ONLY,
{% endif %}response={{responseName}}.class
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.jwoglom.pumpx2.pump.messages.response.control;

import com.google.common.base.Preconditions;
import com.jwoglom.pumpx2.pump.messages.bluetooth.Characteristic;
import com.jwoglom.pumpx2.pump.messages.helpers.Bytes;
import com.jwoglom.pumpx2.pump.messages.Message;
import com.jwoglom.pumpx2.pump.messages.MessageType;
import com.jwoglom.pumpx2.pump.messages.annotations.MessageProps;
import com.jwoglom.pumpx2.pump.messages.models.KnownApiVersion;
import com.jwoglom.pumpx2.pump.messages.models.SupportedDevices;
import com.jwoglom.pumpx2.pump.messages.request.control.SetSleepScheduleRequest;

import java.math.BigInteger;

@MessageProps(
opCode=-49,
size=1,
type=MessageType.RESPONSE,
characteristic=Characteristic.CONTROL,
signed=true,
minApi=KnownApiVersion.MOBI_API_V3_5,
supportedDevices=SupportedDevices.MOBI_ONLY,
request=SetSleepScheduleRequest.class
)
public class SetSleepScheduleResponse extends Message {

private int status;

public SetSleepScheduleResponse() {}

public SetSleepScheduleResponse(byte[] raw) {
parse(raw);
}

public SetSleepScheduleResponse(int status) {
parse(buildCargo(status));
}

public void parse(byte[] raw) {
raw = this.removeSignedRequestHmacBytes(raw);
Preconditions.checkArgument(raw.length == props().size());
this.cargo = raw;
this.status = raw[0];

}


public static byte[] buildCargo(int status) {
return Bytes.combine(
new byte[]{ (byte) status }
);
}

public int getStatus() {
return status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import java.math.BigInteger;
opCode={{responseOpcode}},
size={{responseSize}},
type=MessageType.RESPONSE,
characteristic=Characteristic.{{catUuid}},
characteristic=Characteristic.{{catUuid}},{% if catUuid == "CONTROL" %}
signed=true,{% endif %}
{% if mobiOnly %}minApi=KnownApiVersion.MOBI_API_V3_5,
supportedDevices=SupportedDevices.MOBI_ONLY,
{% endif %}request={{requestName}}.class
Expand All @@ -34,7 +35,8 @@ public class {{responseName}} extends Message {
{% endif %}{% endfor %}{% endif %}
}

public void parse(byte[] raw) {
public void parse(byte[] raw) {{% if catUuid == "CONTROL" %}
raw = this.removeSignedRequestHmacBytes(raw);{% endif %}
Preconditions.checkArgument(raw.length == props().size());
this.cargo = raw;
{% for arg in responseArgs %}{% if arg.name != "raw" %}this.{{ arg.name }} = {% if arg.size == 1 %}raw{% if arg.type == "byte[]" %}{% else %}[{{ arg.index }}]{% endif %}{% if arg.type == "boolean" %} != 0{% endif %};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package com.jwoglom.pumpx2.pump.messages.request.control;

import static com.jwoglom.pumpx2.pump.messages.MessageTester.assertHexEquals;
import static com.jwoglom.pumpx2.pump.messages.MessageTester.initPumpState;

import static org.junit.Assert.assertEquals;

import com.jwoglom.pumpx2.pump.messages.MessageTester;
import com.jwoglom.pumpx2.pump.messages.PacketArrayList;
import com.jwoglom.pumpx2.pump.messages.bluetooth.CharacteristicUUID;
import com.jwoglom.pumpx2.pump.messages.models.MinsTime;
import com.jwoglom.pumpx2.pump.messages.models.MultiDay;
import com.jwoglom.pumpx2.pump.messages.request.control.SetSleepScheduleRequest;
import com.jwoglom.pumpx2.pump.messages.response.currentStatus.ControlIQSleepScheduleResponse;

import org.apache.commons.codec.DecoderException;
import org.junit.Test;

public class SetSleepScheduleRequestTest {
@Test
public void testSetSleepScheduleRequest_set0_rawBytes() throws DecoderException {
// TimeSinceResetResponse[currentTime=512443266,pumpTimeSinceReset=1905836,cargo={-126,67,-117,30,-84,20,29,0}]
initPumpState(PacketArrayList.IGNORE_INVALID_HMAC, 1905836L);

SetSleepScheduleRequest expected = new SetSleepScheduleRequest(
new byte[]{0,1,127,0,0,-97,5,3}
);

// Sleep Schedule 1 on (StartTime:00:00, End Time 23:59, Repeat: Su-Sa, On) (start time, end time, repeat days, on/off)
SetSleepScheduleRequest parsedReq = (SetSleepScheduleRequest) MessageTester.test(
// 2024-03-28T00:21:36.204000+00:00
"02a2cea22000017f00009f0503ff978b1e828836",
-94,
3,
CharacteristicUUID.CONTROL_CHARACTERISTICS,
expected,
"01a222d3b77a25443cc291be989dc841df1a8a75",
"00a266"
);

// for: ControlIQSleepScheduleResponse[schedule0=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=127,enabled=1,endTime=1439,startTime=0],schedule1=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=48,enabled=0,endTime=540,startTime=1320],schedule2=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=0,enabled=0,endTime=420,startTime=1380],schedule3=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=0,enabled=0,endTime=420,startTime=1380],cargo={1,127,0,0,-97,5,0,48,40,5,28,2,0,0,100,5,-92,1,0,0,100,5,-92,1}]

assertHexEquals(expected.getCargo(), parsedReq.getCargo());
assertEquals(0, parsedReq.getSlot());
assertEquals(1, parsedReq.getSleepSchedule().getEnabled());
assertEquals(MultiDay.ALL_DAYS, parsedReq.getSleepSchedule().activeDays());
assertEquals(new MinsTime(0), parsedReq.getSleepSchedule().startTime());
assertEquals(new MinsTime(23, 59), parsedReq.getSleepSchedule().endTime());
assertEquals(3, parsedReq.getFlag());
}

@Test
public void testSetSleepScheduleRequest_disable0() throws DecoderException {
// TimeSinceResetResponse[currentTime=512443266,pumpTimeSinceReset=1905836,cargo={-126,67,-117,30,-84,20,29,0}]
initPumpState(PacketArrayList.IGNORE_INVALID_HMAC, 1905836L);

SetSleepScheduleRequest expected = new SetSleepScheduleRequest(
new byte[]{0,0,127,0,0,-97,5,3}
);

// Sleep Schedule 1 off
SetSleepScheduleRequest parsedReq = (SetSleepScheduleRequest) MessageTester.test(
// 2024-03-28T00:21:54.478000+00:00
"02abceab2000007f00009f050311988b1ef9651b",
-85,
3,
CharacteristicUUID.CONTROL_CHARACTERISTICS,
expected,
"01abc15275490acd94a3416d8cfc038cd7ff461a",
"00ab0c"
);

// for: ControlIQSleepScheduleResponse[schedule0=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=127,enabled=0,endTime=1439,startTime=0],schedule1=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=48,enabled=0,endTime=540,startTime=1320],schedule2=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=0,enabled=0,endTime=420,startTime=1380],schedule3=ControlIQSleepScheduleResponse.SleepSchedule[activeDays=0,enabled=0,endTime=420,startTime=1380],cargo={0,127,0,0,-97,5,0,48,40,5,28,2,0,0,100,5,-92,1,0,0,100,5,-92,1}]

assertHexEquals(expected.getCargo(), parsedReq.getCargo());
assertEquals(0, parsedReq.getSlot());
assertEquals(0, parsedReq.getSleepSchedule().getEnabled());
assertEquals(MultiDay.ALL_DAYS, parsedReq.getSleepSchedule().activeDays());
assertEquals(new MinsTime(0), parsedReq.getSleepSchedule().startTime());
assertEquals(new MinsTime(23, 59), parsedReq.getSleepSchedule().endTime());
assertEquals(3, parsedReq.getFlag());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.jwoglom.pumpx2.pump.messages.response.control;

import static com.jwoglom.pumpx2.pump.messages.MessageTester.assertHexEquals;
import static com.jwoglom.pumpx2.pump.messages.MessageTester.initPumpState;

import static org.junit.Assert.assertEquals;

import com.jwoglom.pumpx2.pump.messages.MessageTester;
import com.jwoglom.pumpx2.pump.messages.PacketArrayList;
import com.jwoglom.pumpx2.pump.messages.bluetooth.CharacteristicUUID;

import org.apache.commons.codec.DecoderException;
import org.junit.Test;

public class SetSleepScheduleResponseTest {
@Test
public void testSetSleepScheduleResponse_mobi() throws DecoderException {
// TimeSinceResetResponse[currentTime=512443266,pumpTimeSinceReset=1905836,cargo={-126,67,-117,30,-84,20,29,0}]
initPumpState(PacketArrayList.IGNORE_INVALID_HMAC, 1905836L);

SetSleepScheduleResponse expected = new SetSleepScheduleResponse(
new byte[]{0}
);

SetSleepScheduleResponse parsedRes = (SetSleepScheduleResponse) MessageTester.test(
// Untitled_2_Live_-_Humans_iPhone
"00a2cfa2190099438b1e67bcbb479d9ba61f4ce07e3bfebf2305b87092ed11b4",
-94,
1,
CharacteristicUUID.CONTROL_CHARACTERISTICS,
expected
);

assertHexEquals(expected.getCargo(), parsedRes.getCargo());
assertEquals(0, parsedRes.getStatus());
}
}

0 comments on commit b38a798

Please sign in to comment.