forked from Michel2/pytimex
Initial date format support for 150/150s and initial wristapp support.
This commit is contained in:
+79
-14
@@ -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
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user