Initial date format support for 150/150s and initial wristapp support.

This commit is contained in:
Antti Huhtala
2022-04-03 16:34:44 +03:00
parent 4e4fd0d098
commit 1cd3fd3886
3 changed files with 164 additions and 22 deletions
+79 -14
View File
@@ -321,24 +321,24 @@ Versions: 1
No payload. Marks end of DATA1 packets.
### 0x93 - subtype 1 - CLEAR_EEPROM
### 0x93 - Subtype 1 - CLEAR_EEPROM
Versions: 3, 4
| Byte | Description |
| ---- | ------------------------ |
| 1 | subtype code, 1 = EEPROM |
| 1 | Subtype code, 1 = EEPROM |
Clear EEPROM meta data from watch SRAM. Watch forgets how to interpret
data in EEPROM. However, actual data in EEPROM remains in place.
data in EEPROM. However, actual data in EEPROM is not touched.
### 0x90 - subtype 1 - START_EEPROM
### 0x90 - Subtype 1 - START_EEPROM
Versions: 3, 4
| Byte | Description |
| ----- | ----------------------------------------------- |
| 1 | subtype code, 1 = EEPROM |
| 1 | Subtype code, 1 = EEPROM |
| 2 | Number of DATA_EEPROM packets to be transmitted |
| 3->4 | Address to start of appointment data |
| 5->6 | Address to start of todo data |
@@ -353,6 +353,12 @@ Versions: 3, 4
Gets the watch ready to receive a sequence of EEPROM data.
The addresses in 3->4, 5->6, 7->8 and 9->10 point to where those particular
pieces of data begins at on the EEPROM. The addresses are given so that the
first byte is the MSB of the address while the second byte is the LSB. The
beginning of the EEPROM is denoted by address 0x0236. The addressing is in
bytes.
Byte 15 is the year of the first occurring appointment entry. The watch
uses this for computing the day of the week an appointment occurs on.
The watch assumes that the entries are uploaded in chronological order,
@@ -361,32 +367,35 @@ and that the gap between adjacent appointments is less than a year.
There appears to be a bug in how the watch calculates days of the week
for the appointments. Wrapping around from the final appointment back
to the first will show an incorrect day of the week for all appointments.
Re-enteering the appointment mode will again calculate the days correctly.
Re-entering the appointment mode, or scanning through the dates will again
calculate the days correctly.
Byte 16 indicates how long before appointments, in 5 minute intervals,
the alarm will sound. Set to 0xFF for no alarm
### 0x91 - subtype 1 - DATA_EEPROM
### 0x91 - Subtype 1 - DATA_EEPROM
Versions: 3, 4
| Byte | Description |
| ------ | ---------------------------------- |
| 1 | subtype code, 1 = EEPROM |
| 1 | Subtype code, 1 = EEPROM |
| 2 | Sequence ID (starts at 1) |
| 3->len | EEPROM payload data |
The data in the payload consists of records in the EEPROM data record
formats. The maximum length of the payload is 32 bytes, leading to a
The maximum length of the payload is 32 bytes, leading to a
maximum packet size of 38 bytes.
### 0x92 - subtype 1 - END_EEPROM
The data in the payload consists of records in the EEPROM data record
formats.
### 0x92 - Subtype 1 - END_EEPROM
Versions: 3, 4
| Byte | Description |
| ---- | ------------------------ |
| 1 | subtype code, 1 = EEPROM |
| 1 | Subtype code, 1 = EEPROM |
Ends the EEPROM transmission sequence.
@@ -472,6 +481,62 @@ long.
| 4->len | Packed string |
### 0x93 - Subtype 2 - CLEAR_WRISTAPP
Versions: 3, 4
| Byte | Description |
| ---- | -------------------------- |
| 1 | Subtype code, 2 = WRISTAPP |
Clear Wristapp from memory.
### 0x90 - Subtype 2 - START_WRISTAPP
Versions: 3, 4
| Byte | Description |
| ----- | ------------------------------------------------- |
| 1 | Subtype code, 2 = WRISTAPP |
| 2 | Number of DATA_WRISTAPP packets to be transmitted |
| 3 | Value written to address 0x010e in SRAM |
Starts a transfer sequence to upload a new Wristapp.
Value put in byte 3 will be written to 0x010e in watch SRAM. The meaning of
this is unknown. Original Timex software appears to always write a 0x01.
It appears writing any other value to byte 3 will cause the watch to accept the
wristapp in the transfer, but it will not launch it.
### 0x91 - Subtype 1 - DATA_WRISTAPP
Version 3, 4
| Byte | Description |
| ------ | ---------------------------------- |
| 1 | Subtype code, 2 = WRISTAPP |
| 2 | Sequence ID (starts at 1) |
| 3->len | Wristapp payload data |
The maximum length of the payload is 32 bytes, leading to a
maximum packet size of 38 bytes.
The data in the payload consists of data bytes to be written to SRAM
starting from address 0x0110. There appears to be no protection against
overflow. Writing past the Wristapp area will first corrupt the sound
area and then continue onto the upper RAM area.
### 0x92 - Subtype 2 - END_WRISTAPP
Versions: 3, 4
| Byte | Description |
| ---- | -------------------------- |
| 1 | Subtype code, 2 = Wristapp |
Ends the Wristapp transmission sequence.
### 0x50 - ALARM
Versions: 1, 3
@@ -504,7 +569,7 @@ no 0x70 packets need to be sent.
### 0x70 - MEM
Versions: 1, 3, 4, 9
Versions: 1, 3?, 4?, 9?
Didn't know what to call this. Sent after silent alarms on version 1,
not sent on version 3 by the original Timex software. Seems to be sent
@@ -523,7 +588,7 @@ of bytes may be written.
### 0x71 - BEEPS
Versions: 1?, 3
Versions: 1?, 3?, 4?
| Byte | Description |
| ---- | ---------------------------------- |
+59 -7
View File
@@ -13,7 +13,14 @@ DL50 = WatchModel('DL50', 1) # I think this uses the same protocol as the 70
DL70 = WatchModel('DL70', 1)
DL150 = WatchModel('DL150', 3)
DL150s = WatchModel('DL150s', 4)
DLIRONMAN = WatchModel('DLIRONMAN', 9)
MMDDYY_DASHES = 0
DDMMYY_DASHES = 1
YYMMDD_DASHES = 2
MMDDYY_DOTS = 4
DDMMYY_DOTS = 5
YYMMDD_DOTS = 6
# Month name lookup
monthNamesAbbr = [
@@ -104,9 +111,10 @@ class TimexAnniversary:
class TimexTimezone:
def __init__(self, offset=0, format=24, name=""):
def __init__(self, offset=0, format=24, name="", dateformat=MMDDYY_DASHES):
self.offset = offset
self.format = format
self.dateformat = dateformat
self.name = name
@property
@@ -119,8 +127,27 @@ class TimexTimezone:
raise Exception("Time format must be 12 or 24 hours")
self._format = f
@property
def dateformat(self):
return self._dateformat
@dateformat.setter
def dateformat(self, f):
if not f in [0,1,2,4,5,6]:
raise Exception("Date format not valid")
self._dateformat = f
def __str__(self):
return "Time zone with offset UTC{:+} named \"{}\", {} hour format".format(offset, name, format)
dateformatstring = {
0: "MM-DD-YY",
1: "DD-MM-YY",
2: "YY-MM-DD",
4: "MM.DD.YY",
5: "DD.MM.YY",
6: "YY.MM.D"
}.get(self.dateformat, "invalid dateformat")
return "Time zone with offset UTC{:+} named \"{}\", {} hour format, date format {}".format(self.offset, self.name, self.format, dateformatstring)
class TimexAlarm:
@@ -150,6 +177,17 @@ class TimexAlarm:
return "Alarm at {:02d}:{:02d} on the {} of {}, label \"{}\", {}".format(
self.hour, self.minute, self.day, monthNamesAbbr[self.month], self.label, audible_str)
class TimexWristapp:
def __init__(self, filename=None, databytes=None):
if databytes != None:
self.databytes = databytes
elif filename != None:
f = open(filename,"rb")
self.databytes = list(f.read())
f.close()
else:
raise Exception("You need to specify either a file name or the data")
class TimexData:
def __init__(self, model=DL70):
@@ -163,20 +201,21 @@ class TimexData:
TimexTimezone(0, 24, "utc"),
TimexTimezone(-5, 12, "est")
]
self.wristapp = None
# Number of seconds to add to time when blasting, to compensate
# for the time between building the packet and the watch
# receiving it.
self.secondsOffset=8
self.model=model
def setTimezone(self, tzno, offset, format, name):
def setTimezone(self, tzno, offset, format, name, dateformat=MMDDYY_DASHES):
if tzno not in [1,2]:
raise Exception("Time zone number must be 1 or 2!")
if len(name)>3:
raise Exception("Max length for time zone name is 3 characters!")
self.tz[tzno-1] = TimexTimezone(offset, format, name)
self.tz[tzno-1] = TimexTimezone(offset, format, name, dateformat=dateformat)
def addAppointment(self, appointment):
self.appointments.append(appointment)
@@ -233,6 +272,12 @@ class TimexData:
def delAlarm(self, alarm):
self.alarms = [a for a in self.alarms if a != alarm]
def setWristapp(self, filename=None, databytes=None):
self.wristapp = TimexWristapp(filename=filename, databytes=databytes)
def delWristapp(self):
self.wristapp = None
def __bytes__(self):
data = b''
@@ -248,9 +293,10 @@ class TimexData:
data += bytes(makeTZNAME(1, self.tz[0].name))
data += bytes(makeTZNAME(2, self.tz[1].name))
elif self.model.protocol == 3 or self.model.protocol == 4:
data += bytes(makeTIMETZ(1, tz1time, self.tz[0].format, self.tz[0].name))
data += bytes(makeTIMETZ(2, tz2time, self.tz[1].format, self.tz[1].name))
data += bytes(makeTIMETZ(1, tz1time, self.tz[0].format, self.tz[0].name, self.tz[0].dateformat))
data += bytes(makeTIMETZ(2, tz2time, self.tz[1].format, self.tz[1].name, self.tz[1].dateformat))
else:
print("Time upload not implemented for protocol {}".format(self.model.protocol))
if (
len(self.appointments)>0 or
len(self.todos)>0 or
@@ -266,6 +312,12 @@ class TimexData:
else:
print("DATA payload not implemented for protocol {}".format(self.model.protocol))
if self.wristapp != None:
if self.model.protocol == 3 or self.model.protocol == 4:
data += bytes(makeDATA_WRISTAPPcompleteBreakfast(self.wristapp))
else:
print("Wristapps not supported for protocol {}".format(self.model.protocol))
# It's not necessary to send all alarms at once. Though this
# will leave the alarms not explicitly overwritten. So it might
## be a good idea to do that. Or make it an option?
+26 -1
View File
@@ -276,7 +276,7 @@ def makeDATA_EEPROMcompleteBreakfast(appts, todos, phones, anniversaries, appt_a
num_packets = ceil(len(payload)/32)
# CLEAR_EEPROM
eeprompackets = makepkg([0x03, 0x01])
eeprompackets = makepkg([0x93, 0x01])
# START_EEPROM
eeprompackets += makepkg([0x90, 0x01, num_packets] + header)
@@ -293,6 +293,31 @@ def makeDATA_EEPROMcompleteBreakfast(appts, todos, phones, anniversaries, appt_a
return eeprompackets
# Takes a TimexWristapp object.
# Returns the CLEAR_WRISTAPP, START_WRISTAPP, DATA_WRISTAPP and STOP_WRISTAPP packets consistent with the data
def makeDATA_WRISTAPPcompleteBreakfast(wristapp):
payload = wristapp.databytes
num_packets = ceil(len(payload)/32)
# CLEAR_WRISTAPP
wristapppackets = makepkg([0x93, 0x02])
# START_WRISTAPP
wristapppackets += makepkg([0x90, 0x02, num_packets, 0x01])
# DATA_WRISTAPP packets
index = 0
while (payload):
index += 1
wristapppackets += makepkg([0x91, 0x02, index]+payload[:32])
payload = payload[32:]
# END_WRISTAPP
wristapppackets += makepkg([0x92, 0x02])
return wristapppackets
# Pass an ID 1 or 2, a datetime object containing current time in this
# timezone and 12 or 24 for time format
def makeTZ(tzno, tztime, format):