I got step tracking working! It wasn't too hard once I understood that the android app packet parser had state that was kept between packets depending on if it returns true or false.
```python
def bcd_to_decimal(b: int) -> int:
return (((b >> 4) & 15) * 10) + (b & 15)
@dataclass
class SportDetail():
year: int
month: int
day: int
time_index: int
calories: int
steps: int
distance: int
class SportDetailParser():
r"""
Parse SportDetailPacket, of which there will be several
example data:
bytearray(b'C\xf0\x05\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x009')
bytearray(b'C#\x08\x13\x10\x00\x05\xc8\x000\x00\x1b\x00\x00\x00\xa9')
bytearray(b'C#\x08\x13\x14\x01\x05\xb6\x18\xaa\x04i\x03\x00\x00\x83')
bytearray(b'C#\x08\x13\x18\x02\x058\x04\xe1\x00\x95\x00\x00\x00R')
bytearray(b'C#\x08\x13\x1c\x03\x05\x05\x02l\x00H\x00\x00\x00`')
bytearray(b'C#\x08\x13L\x04\x05\xef\x01c\x00D\x00\x00\x00m')
"""
def __init__(self):
self.reset()
def reset(self):
self.new_calorie_protocol = False
self.index = 0
self.details: list[SportDetail] = []
def parse(self, packet: bytearray):
assert len(packet) == 16
assert packet[0] == CMD_GET_STEP_SOMEDAY
if self.index == 0 and packet[1] == 255:
self.reset()
return
if self.index == 0 and packet[1] == 240:
if packet[3] == 1:
self.new_calorie_protocol = True
self.index += 1
else:
year = bcd_to_decimal(packet[1]) + 2000
month = bcd_to_decimal(packet[2])
day = bcd_to_decimal(packet[3])
time_index = packet[4]
calories = packet[7] | (packet[8] << 8)
if self.new_calorie_protocol:
calories *= 10
steps = packet[9] | (packet[10] << 8)
distance = packet[11] | (packet[12] << 8)
details = SportDetail(
year=year,
month=month,
day=day,
time_index=time_index,
calories=calories,
steps=steps,
distance=distance
)
print("Details: ", details)
self.details.append(details)
self.index += 1
if packet[5] == packet[6] - 1:
self.reset()
```
This is what I get
```
Details: SportDetail(year=2023, month=8, day=13, time_index=16, calories=2000, steps=48, distance=27)
Details: SportDetail(year=2023, month=8, day=13, time_index=20, calories=63260, steps=1194, distance=873)
Details: SportDetail(year=2023, month=8, day=13, time_index=24, calories=10800, steps=225, distance=149)
Details: SportDetail(year=2023, month=8, day=13, time_index=28, calories=5170, steps=108, distance=72)
Details: SportDetail(year=2023, month=8, day=13, time_index=76, calories=6520, steps=126, distance=90)
```
Calories seem pretty high and the time_index is a bit mysterious. Pretty sure this is across multiple days
Anyway, I want to set the time so let's look at the `SetTimeReq`
```java
public SetTimeReq(int i) {
super((byte) 1);
this.mLanguage = (byte) 0;
this.mData = new byte[7];
this.mLocaleMap = new HashMap();
initMap();
setLanguage();
Calendar calendar = Calendar.getInstance();
calendar.add(13, i);
this.mData[0] = BLEDataFormatUtils.decimalToBCD(calendar.get(1) % 2000);
this.mData[1] = BLEDataFormatUtils.decimalToBCD(calendar.get(2) + 1);
this.mData[2] = BLEDataFormatUtils.decimalToBCD(calendar.get(5));
this.mData[3] = BLEDataFormatUtils.decimalToBCD(calendar.get(11));
this.mData[4] = BLEDataFormatUtils.decimalToBCD(calendar.get(12));
this.mData[5] = BLEDataFormatUtils.decimalToBCD(calendar.get(13));
}
```
Using `jshell` (which I have installed somehow?) to figure out the exact fields. `1` is the year. `2` is the 0-indexed month. `5` is the day. `11` is the hour. `12` is the minute. `13` is the second. I guess we add an offset of either 0 or 1 for some reason.
Looks like the language is also set?
```java
private void initMap() {
this.mLocaleMap.put("zh_CN", 0);
this.mLocaleMap.put("en", 1);
this.mLocaleMap.put("zh_HK", 2);
this.mLocaleMap.put("zh_TW", 2);
this.mLocaleMap.put("el", 3);
this.mLocaleMap.put(Localization.language, 4);
this.mLocaleMap.put("de", 5);
this.mLocaleMap.put("it", 6);
this.mLocaleMap.put("es", 7);
this.mLocaleMap.put("nl", 8);
this.mLocaleMap.put("pt", 9);
this.mLocaleMap.put("ru", 10);
this.mLocaleMap.put("tr", 11);
this.mLocaleMap.put("ja", 12);
this.mLocaleMap.put("ko", 13);
this.mLocaleMap.put("pl", 14);
this.mLocaleMap.put("ro", 15);
this.mLocaleMap.put("ar", 16);
this.mLocaleMap.put("th", 17);
this.mLocaleMap.put("vi", 18);
this.mLocaleMap.put("in", 19);
this.mLocaleMap.put("hi", 20);
this.mLocaleMap.put("cs", 21);
this.mLocaleMap.put("sk", 22);
this.mLocaleMap.put("hu", 23);
this.mLocaleMap.put("iw", 24);
this.mLocaleMap.put("hr", 25);
this.mLocaleMap.put("sl", 26);
this.mLocaleMap.put("ur", 35);
}
@Override // com.oudmon.ble.base.communication.req.BaseReqCmd
protected byte[] getSubData() {
byte[] bArr = this.mData;
bArr[6] = this.mLanguage;
return bArr;
}
public void setLanguage() {
String language = Locale.getDefault().getLanguage();
if (language.startsWith("zh")) {
language = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry().toUpperCase();
}
Integer num = this.mLocaleMap.get(language);
int intValue = num == null ? 1 : num.intValue();
AwLog.i(Author.HeZhiYuan, "SetTimeReq -> mLanguage: " + language + ", value: " + num + ", result: " + intValue);
this.mLanguage = (byte) intValue;
}
```
So the first 5 bytes are BCD encoded date/time and the 6th byte is the language based on this mapping.
Here's the python equivalent
```python
CMD_SET_TIME = 1
def byte_to_bcd(b: int) -> int:
assert b < 99
assert b > 0
tens = b // 10
ones = b % 10
return (tens << 4) | ones
def set_time_packet(target: datetime | None = None) -> bytearray:
if target is None:
target = datetime.now()
data = bytearray(7)
data[0] = byte_to_bcd(target.year % 2000)
data[1] = byte_to_bcd(target.month)
data[2] = byte_to_bcd(target.day)
data[3] = byte_to_bcd(target.hour)
data[4] = byte_to_bcd(target.minute)
data[5] = byte_to_bcd(target.second)
data[6] = 1 # set language to english, 0 is chinese
return make_packet(CMD_SET_TIME, data)
```
This seems to have worked, the next day when I ask for sport details, the year, month and date are correct. (well, the month was off by 1 because I didn't read the docs)
This does seem to return an error though
```
bytearray(b'/\xf1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 ')
bytearray(b'\x01\x00\x01\x00"\x00\x00\x00\x00\x01\x000\x01\x00\x10f')
```
The initial `\xf1` byte in the packet has the high bit set which indicates an error