Code Monkey home page Code Monkey logo

timex_datalink_client's Introduction

Timex Datalink library for Ruby

Here is a fully-tested, feature-complete, and byte-for-byte perfect reimplementation of all the various Timex Datalink client software as a Ruby library! This library supports every Datalink device manufactured, which includes protocols 1, 3, 4, 6, 7, and 9!

These devices have been tested to work with this library:

  • Timex Datalink 50 (protocol 1)
  • Timex Datalink 70 (protocol 1)
  • Timex Datalink 150 (protocol 3)
  • Timex Datalink 150s (protocol 4)
  • Timex Ironman Triathlon (protocol 9)
  • Motorola Beepwear Pro (protocol 6)
  • Franklin Rolodex Flash PC Companion RFLS-8 (protocol 1)
  • Royal FL95 PC Organizer (protocol 1)
  • DSI e-BRAIN (protocol 7)

What is the Timex Datalink?

The Timex Datalink is a watch that was introduced in 1994 that is essentially a small PDA on your wrist. The early models (supported by this software) have an optical sensor on the top of the face that receives data via visible light.

The original data transfer method involves drawing patterns of lines on a CRT monitor for the watch to receive with its optical sensor. CRTs use electron guns that draw scan lines one-by-one from top to bottom, then they return to the top in preparation for the next frame. This means that the electron guns turn on when they're drawing a white line, and and turn off when they're drawing the black background. This produces flashing light as the graphics are drawn, which is ultimately received by the optical sensor and decoded by the Timex Datalink device.

Have a CRT monitor? Use this library with timex_datalink_crt to transfer data with your CRT!

For laptop users, Timex also offered the Datalink Notebook Adapter. Instead of using a CRT monitor, the Notebook Adapter simply flashed a single LED light. This adapter is fully supported by the Timex Datalink software, and sends the same data as a CRT.

This library communicates with the Datalink Notebook Adapter to emit data to your Timex Datalink watch. Don't have a Notebook Adapter? Use a Teensy LC instead!

As a fun tidbit, these watches are flight certified by NASA and is one of four watches qualified by NASA for space travel! Here's a shot of James H. Newman wearing a Datalink watch on the Space Shuttle for STS-88!

In addition, the Datalink protocol is also used in some other watches, organizers, and toys, i.e. the Motorola Beepwear Pro, Royal FL95, Tiger PDA2000, Franklin Rolodex Flash PC Companion RFLS-8, and DSI e-BRAIN 69006.

Installing Ruby and the timex_datalink_client gem

If you need to install Ruby, follow the Ruby installation instructions first. The oldest supported version is 3.1.0, so make sure to have Ruby 3.1.0 or greater installed.

Then, with Ruby installed, run this command to install the timex_datalink_client gem:

gem install timex_datalink_client

You're done! From here, continue reading the documentation for which protocol to use and follow the code examples below.

Determining the protocol to use

On Timex Datalink watches, press the MODE button until "COMM MODE" is displayed. "COMM READY" will appear. This is sometimes accompanied by a version number. Use the table below to identify the protocol.

Watch display Protocol compatibility
Use protocol 1 models in TimexDatalinkClient::Protocol1
Use protocol 3 models in TimexDatalinkClient::Protocol3
Use protocol 4 models in TimexDatalinkClient::Protocol4
Use protocol 9 models in TimexDatalinkClient::Protocol9
Use protocol 6 models in TimexDatalinkClient::Protocol6

During data transmission, the "start" packet of each protocol will announce the protocol number to the device. If the protocol doesn't match the device, the screen will display "PC-WATCH MISMATCH" and safely abort the data transmission.

Most non-Timex devices use protocol 1, so start with protocol 1 if the protocol can't be identified.

Protocol documentation

Each protocol is individually documented with their own code examples:

Tuning data transfer performance

After every byte is sent to the watch, a small delay is necessary before advancing to the next byte. This gives the watch time to decode and store the incoming data. In addition, an additional delay is necessary after sending a packet of data (bytes that represent a piece of data, i.e. an alarm).

The byte and packet sleep time defaults to the same rate of the Timex Datalink software for parity. This is 0.025 seconds per byte, and 0.25 seconds per packet. These two sleep times can be tuned with the byte_sleep and packet_sleep keywords when creating a TimexDatalinkClient instance.

In practice, much smaller values can be used for a much higher data rate. In testing, these values seem to work reliably with the Teensy LC Notebook Adapter:

timex_datalink_client = TimexDatalinkClient.new(
  serial_device: "/dev/ttyACM0",
  models: models,
  byte_sleep: 0.008,
  packet_sleep: 0.06,
  verbose: true
)

timex_datalink_client's People

Contributors

synthead avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

timex_datalink_client's Issues

Add Timer model to protocol 9

Here's some example data:

image

Bytes from above:

11 43 01 00 14 00 00 01 1C 1D 24 1A 1D 1B 24 8C 22
11 43 02 00 14 00 00 02 17 0D 24 1A 1D 1B 24 0A DF
11 43 03 00 14 00 00 11 0A 15 0F 1D 12 16 0E 75 C2
11 43 04 00 14 00 00 03 1B 0D 24 1A 1D 1B 24 00 DB
11 43 05 00 14 00 00 04 1D 11 24 1A 1D 1B 24 3B FC

Add Time model for protocol 1

Add Protocol1::Time to sync time data for protocol 1. This will support syncing time for Datalink 50 and Datalink 70 watches.

Allow custom time zone name

The time zone that is visible on the watch is currently hard-coded to follow whatever is set on the time param passed to the Time model:

def timezone_characters
chars_for(time.zone.downcase, length: 3, pad: true)
end

The time zone name is simply arbitrary text of 3 characters, and there isn't any reason why this needs to be a time zone name.

Allow the time zone name to be a user-issued field.

Needless truncate logic in Eeprom::PhoneNumber

This explicitly truncates the phone number to 12 places:

def number_with_type_truncated
number_with_type = "#{number} #{type}"
padded_number_with_type = number_with_type.rjust(PHONE_DIGITS)
padded_number_with_type[0..PHONE_DIGITS - 1]
end
def number_with_type_characters
phone_chars_for(number_with_type_truncated)
end

However, phone_chars_for does this already, so there's no need to truncate this value.

def phone_chars_for(string_chars)
chars = chars_for(string_chars, char_map: PHONE_CHARS, length: 12)
packed_int = chars.each_with_index.sum do |char, index|
char << (4 * index)
end
packed_int.digits(256)
end

Remove the needless truncate logic from Eeprom::PhoneNumber

Make SoundTheme accept raw sound data

As of now, SoundTheme only accepts data from SPC files, which prefixes the sound data with a file header.

Allow the user to submit raw sound data in addition to the entire file contents of a sound file.

Truncate strings to ensure that they are within data limits

Every string in the Datalink 150 has a maximum length, but many strings in the lib are not truncated, like so:

def message_characters
eeprom_chars_for(message)
end

(the above uses these methods)

def chars_for(string_chars, char_map: CHARS)
string_chars.each_char.map do |string_char|
char_map.index(string_char)
end
end
def eeprom_chars_for(string_chars)
chars = chars_for(string_chars).append(EEPROM_TERMINATOR)
packed_int = chars.each_with_index.sum do |char, index|
char << (6 * index)
end
packed_int.digits(256)
end

It's possible to pass strings that are longer than the possible limit and write invalid data.

Truncate these strings to their allowed limits to prevent writing invalid data.

Make Eeprom's appointment_notification parameter more clear

As of now, Eeprom's appointment_notification parameter is not very clear:

APPOINTMENT_NO_NOTIFICATION = 0xff
attr_accessor :appointments, :anniversaries, :phone_numbers, :lists, :appointment_notification
def initialize(appointments: [], anniversaries: [], phone_numbers: [], lists: [], appointment_notification: APPOINTMENT_NO_NOTIFICATION)
@appointments = appointments
@anniversaries = anniversaries
@phone_numbers = phone_numbers
@lists = lists
@appointment_notification = appointment_notification
end

The Timex Datalink software uses set values from a drop-down list that appear to be multiples of 15 minute values, and 0xff is used for no notification. Determine exactly what these are, and possibly add some constants that the user can use instead of what's ultimately a magic number.

Use slower byte_sleep value in README.md performance section

This is the documentation for increasing data performance in README.md as of this writing:

## Tuning data transfer performance
After every byte is sent to the watch, a small delay is necessary before advancing to the next byte. This gives the
watch time to decode and store the incoming data. In addition, an additional delay is necessary after sending a packet
of data (bytes that represent a piece of data, i.e. an alarm).
The byte and packet sleep time defaults to the same rate of the Timex Datalink software for parity. This is 0.025
seconds per byte, and 0.25 seconds per packet. These two sleep times can be tuned with the `byte_sleep` and
`packet_sleep` keywords when creating a `TimexDatalinkClient` instance.
In practice, much smaller values can be used for a much higher data rate. In testing, these values seem to work
reliably with the [Teensy LC Notebook Adapter](https://github.com/synthead/timex-datalink-arduino):
```ruby
timex_datalink_client = TimexDatalinkClient.new(
serial_device: "/dev/ttyACM0",
models: models,
byte_sleep: 0.006,
packet_sleep: 0.06,
verbose: true
)
```

On the Timex 70301 (Datalink 50) and Franklin Rolodex Flash PC Companion RFLS-8 (which are both protocol 1 devices), the 0.006 byte_sleep option is a little too aggressive and doesn't seem to work most of the time. I haven't seen 0.008 fail with these devices yet with any device, and this difference is hardly noticeable.

Update README.md to use a byte_sleep value of 0.008 in the performance tuning section.

"Small square" special character is interpreted as string terminator in EEPROM model strings

Here are CHARS and EEPROM_TERMINATOR:

CHARS = "0123456789abcdefghijklmnopqrstuvwxyz !\"#$%&'()*+,-./:\\;=@?_|<>[]"
EEPROM_TERMINATOR = 0x3f

chars_for will happily convert ] in strings to value 63, which is the last value of CHARS:

def chars_for(string_chars, char_map: CHARS, length: nil, pad: false)
formatted_chars = string_chars.downcase[0..length.to_i - 1]
formatted_chars = formatted_chars.ljust(length) if pad
formatted_chars.each_char.map do |char|
char_map.index(char)
end
end

This is fine, except when packing strings in eeprom_chars_for, 63 (which is 0x3f) is expected to be the terminator. This means that this method will add EEPROM string terminators in place of ]:

def eeprom_chars_for(string_chars)
chars = chars_for(string_chars, length: 31).append(EEPROM_TERMINATOR)
packed_int = chars.each_with_index.sum do |char, index|
char << (6 * index)
end
packed_int.digits(256)
end

On the Timex Datalink 150, strings with this value simply end whenever this value is seen (as opposed to displaying the "small square" special character).

It's impossible to display the "small square" character in EEPROM strings, and its value produces unpredictable results. Make eeprom_chars_for replace ] with some other placeholder character (i.e. an empty space).

Make all strings case-insensitive

If a model uses a mixture of uppercase and lowercase characters in their strings, i.e.

TimexDatalinkClient::Eeprom::PhoneNumber.new(
  name: "Marty McFly",
  number: "1112223333",
  type: "h"
)

...then the char_for helper will not match some of the uppercase characters:

CHARS = "0123456789abcdefghijklmnopqrstuvwxyz !\"#$%&'()*+,-./:\\;=@?ABCDEF"
EEPROM_TERMINATOR = 0x3f
PHONE_CHARS = "0123456789cfhpw "
def chars_for(string_chars, char_map: CHARS, length: nil, pad: false)
formatted_chars = string_chars[0..length.to_i - 1]
formatted_chars = formatted_chars.ljust(length) if pad
formatted_chars.each_char.map do |char|
char_map.index(char)
end
end

The Datalink 150 has no lowercase characters, and is case-insensitive. Make all strings case-insensitive so that uppercase and lowercase characters transfer correctly to the watch.

Namespace current models to Protocol3

The current models only support the Datalink protocol 3. Protocol 1 (on the Timex Datalink 50 and 70) has different packet formats for most models, so it'd be best to namespace the protocols explicitly.

This is the current model pattern that exists now:

time1 = Time.now
time2 = time1.dup.utc

models = [
  TimexDatalinkClient::Sync.new(length: 200),
  TimexDatalinkClient::Start.new,
  TimexDatalinkClient::Time.new(zone: 1, is_24h: false, date_format: 2, time: time1),
  TimexDatalinkClient::Time.new(zone: 2, is_24h: true, date_format: 2, time: time2),
  TimexDatalinkClient::End.new
]

client = TimexDatalinkClient.new(
  serial_device: "/dev/ttyACM0",
  models: models
)

client.write

Here is what I propose:

time1 = Time.now
time2 = time1.dup.utc

models = [
  TimexDatalinkClient::Protocol3::Sync.new(length: 200),
  TimexDatalinkClient::Protocol3::Start.new,
  TimexDatalinkClient::Protocol3::Time.new(zone: 1, is_24h: false, date_format: 2, time: time1),
  TimexDatalinkClient::Protocol3::Time.new(zone: 2, is_24h: true, date_format: 2, time: time2),
  TimexDatalinkClient::Protocol3::End.new
]

client = TimexDatalinkClient.new(
  serial_device: "/dev/ttyACM0",
  models: models
)

client.write

Then, for protocol 1 devices, a pattern like this could be used. Of course, it would be tweaked to accommodate whatever differences there are between protocol 3 and protocol 1.

time1 = Time.now
time2 = time1.dup.utc

models = [
  TimexDatalinkClient::Protocol1::Sync.new(length: 200),
  TimexDatalinkClient::Protocol1::Start.new,
  TimexDatalinkClient::Protocol1::Time.new(zone: 1, is_24h: false, date_format: 2, time: time1),
  TimexDatalinkClient::Protocol1::Time.new(zone: 2, is_24h: true, date_format: 2, time: time2),
  TimexDatalinkClient::Protocol1::End.new
]

client = TimexDatalinkClient.new(
  serial_device: "/dev/ttyACM0",
  models: models
)

client.write

Add SoundOptions model for protocol 9

Add SoundOptions model for protocol 9. It looks like this in the Ironman software:

image

From a logic analyzer, here are the packets for start, options, and end with no sounds on:

07 20 00 00 09 06 7E
05 32 01 A1 C4
04 21 D8 C2

...with hourly chime on:

07 20 00 00 09 06 7E
05 32 02 A0 84
04 21 D8 C2

...and button beep on:

07 20 00 00 09 06 7E
05 32 03 60 45
04 21 D8 C2

It looks like the first "data" byte for packet with command 0x32 contains bitmask values for the two options. The hourly chime appears to be 0b01 and the button keep appears to be 0b10.

Add Alarm model for protocol 9

Here are some alarms in the Ironman software:

image

Here's the data from the above, sans the sync bytes:

07 20 00 00 09 06 7E
1A 50 01 00 00 00 00 01 01 02 03 04 05 06 07 08 09 00 01 02 03 04 05 06 52 1B
1A 50 02 00 03 00 04 00 0A 15 0A 1B 16 24 24 02 24 24 24 24 24 24 24 24 7A DF
1A 50 03 03 00 04 05 00 0A 15 0A 1B 16 24 24 03 24 24 24 24 24 24 24 24 EA D5
1A 50 04 00 00 00 00 00 0A 15 0A 1B 16 24 24 04 24 24 24 24 24 24 24 24 F2 17
1A 50 05 00 00 00 00 00 0A 15 0A 1B 16 24 24 05 24 24 24 24 24 24 24 24 AE CA
1A 50 06 00 00 00 00 00 0A 15 0A 1B 16 24 24 06 24 24 24 24 24 24 24 24 4B AD
1A 50 07 00 00 00 00 00 0A 15 0A 1B 16 24 24 07 24 24 24 24 24 24 24 24 17 70
1A 50 08 00 00 00 00 00 0A 15 0A 1B 16 24 24 08 24 24 24 24 24 24 24 24 A7 8E
1A 50 09 00 00 00 00 00 0A 15 0A 1B 16 24 24 09 24 24 24 24 24 24 24 24 FB 53
1A 50 0A 00 00 00 00 00 0A 15 0A 1B 16 24 01 00 24 24 24 24 24 24 24 24 C4 5B
04 21 D8 C2

Use common EEPROM model classes for protocol 1 and protocol 3

While working on #57, I discovered that the EEPROM models between protocol 1 and protocol 3 are identical.

To do this, these things need to be done:

  • Move lib/timex_datalink_client/protocol_3/eeprom to lib/timex_datalink_client/eeprom
  • Move all classes in Protocol3::Eeprom to Eeprom
  • Move tests and update them to use Eeprom from the base TimexDatalinkClient class

TimexDatalinkClient#initialize's models param doesn't include Protocol1 model types in YARDoc

TimexDatalinkClient#initialize models param doesn't include Protocol1 model types in YARDoc:

# @param models [Array<Protocol3::Alarm, Protocol3::Eeprom, Protocol3::End, Protocol3::SoundOptions,
# Protocol3::SoundTheme, Protocol3::Start, Protocol3::Sync, Protocol3::Time, Protocol3::WristApp>] Models to compile
# data for.

Add the Protocol1 classes as accepted types to #initialize.

Add EEPROM class to protocol 9

The Ironman software seems to have shared EEPROM storage like protocols 1 and 3:

image

It's more limited: it only contains a single chrono option and phone book entries, but this probably means that an Eeprom class needs to be added for protocol 9, too.

Replace character map for uppercase characters to special characters

This is the character map for most strings:

CHARS = "0123456789abcdefghijklmnopqrstuvwxyz !\"#$%&'()*+,-./:\\;=@?ABCDEF"

The uppercase characters are problematic because they actually map to special characters on the Datalink 150. This is a smell, because a user might submit a string with uppercase characters with the intent of having readable text on the watch.

Change the map of uppercase characters to special characters that are not [A-Za-z].

Trailing commas in a few places

There are a few trailing commas:

# Compile a packet for a phone number.
#
# @return [Array<Integer>] Array of integers that represent bytes.
def packet
[
number_with_type_characters,
name_characters,
].flatten
end

# Compile packets for an alarm.
#
# @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
def packets
[
[
CPACKET_ALARM,
number,
time.hour,
time.min,
month.to_i,
day.to_i,
audible_integer,
message_characters,
].flatten
]
end

let(:phone_number) do
described_class.new(
name: name,
number: number,
type: type,
)
end

Gracefully handle invalid data

There are quite a few places where it's possible to submit bad data. Sometimes the program will raise uncaught exceptions, and other times, it will render packets with invalid data in it.

Convert invalid data to similar acceptable data when possible. Where it isn't, validate the data and raise custom exceptions classes.

Add TimeName model for protocol 1

Related to #49.

After picking at time data on protocol 1 a bit, I discovered that time is synced differently than protocol 3.

Protocol 3 syncs everything related to a time:

# Compile packets for a time.
#
# @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
def packets
[
[
CPACKET_TIME,
zone,
time.sec,
time.hour,
time.min,
time.month,
time.day,
year_mod_1900,
name_characters,
wday_from_monday,
is_24h_value,
date_format
].flatten
]
end

However, it looks like protocol 1 syncs time data and time name data (typically for time zones) separately. Here is UART-decoded data from the Timex Datalink software sending time data only (with sync bytes omitted):

07 20 00 00 01 C0 7F                    # start protocol 1 data
0D 30 02 14 23 09 0B 16 06 03 01 2F B0  # zone 1 time
0D 30 01 12 23 09 0B 16 06 03 01 F5 24  # zone 2 time
08 31 01 19 0D 1D 2E 68                 # zone 1 time name
08 31 02 0C 0D 1D AE 79                 # zone 2 time name
04 21 D8 C2                             # end data

Add a TimeName model to accommodate for this separate packet type.

Add verbose option

The library takes a little while to write the serial data due to the required sleep statements. It'd be great to have an option to enable verbose output to show the data being sent 👌

Use boolean value for Alarm's audible parameter

Alarm currently expects explicit 1 or a 0 as values for audible. These should be true and false instead.

attr_accessor :number, :audible, :time, :message
def initialize(number:, audible:, time:, message:)
@number = number
@audible = audible
@time = time
@message = message
end
def packets
[
[
CPACKET_ALARM,
number,
time.hour,
time.min,
0,
0,
message_characters,
audible
].flatten
]
end

Add Alarm model for protocol 1

Add Alarm model for protocol 1!

In the Timex Datalink software, it looks like this:

image

The protcol 1 format also supports daily, monthly, and yearly alarms (protocol 3 only supports daily alarms):

image

With the data above, this is what comes out of the Datalink software (taken from a logic analyzer, sync bytes omitted):

07 20 00 00 01 C0 7F
12 50 01 00 00 09 15 0A 15 0A 1B 16 24 01 24 01 02 3D
12 50 02 00 00 00 0B 0A 15 0A 1B 16 24 02 24 01 FD 80
12 50 03 00 00 00 00 0A 15 0A 1B 16 24 03 24 01 19 A0
12 50 04 00 00 00 00 0A 15 0A 1B 16 24 04 24 01 1F 17
12 50 05 00 00 00 00 0A 15 0A 1B 16 24 05 24 01 1E 46
04 21 D8 C2

Add Chrono model to protocol 9

Example data (selected text only):

image

Bytes from above:

20 70 02 40 05 A9 22 5F E6 B2 E8 BB E7 B2 E8 BB E7 BB E8 B2 E7 B2 5C A3 09 26 ED 15 A9 01 32 CD
14 70 02 5A A9 02 14 A9 B6 A9 A4 07 47 B7 A9 CC 74 6F C6 FC
06 23 02 40 D2 F1
05 60 01 C1 F9
13 61 01 00 0E 00 D6 32 00 0A 0A 0A 0A 0A 0A 0A 0A D4 C3
04 62 29 83

Add options for byte and packet sleep times

NotebookAdapter is hard-coded to use these values:

class TimexDatalinkClient
class NotebookAdapter
BYTE_SLEEP = 0.025
PACKET_SLEEP = 0.25

They are used here:

def write(packets)
packets.each do |packet|
packet.each do |byte|
printf("%.2X ", byte) if verbose
serial.write(byte.chr)
sleep(BYTE_SLEEP)
end
sleep(PACKET_SLEEP)
puts if verbose
end
end

These values were set to match the Timex Datalink software. However, after some experimenting, these sleep values can be much, much shorter and still reliably transfer data to a Datalink 150.

Add support to allow user-tunable values.

Add model for Phone Book to protocol 9

Here's some example data (two screenshots due to scrolled content:

image

image

Bytes from above:

20 70 02 40 05 A9 22 5F E6 B2 E8 BB E7 B2 E8 BB E7 BB E8 B2 E7 B2 5C A3 09 26 ED 15 A9 01 32 CD
14 70 02 5A A9 02 14 A9 B6 A9 A4 07 47 B7 A9 CC 74 6F C6 FC
06 23 02 40 D2 F1
05 60 06 03 B8
20 61 01 00 0E 00 16 02 08 24 0C 11 1B 18 17 18 24 0E 21 43 65 87 09 BC 99 B3 71 D8 45 06 EB DD
20 61 02 3F 0E 21 43 65 87 09 CF 99 B3 71 D8 45 06 3F 13 21 43 65 87 09 21 F1 40 14 C6 81 0B 44
20 61 03 24 40 20 0C 44 61 FC 13 21 43 65 87 09 21 F1 40 14 C6 81 24 40 20 0C 44 61 FC 13 F1 B2
20 61 04 99 99 99 99 99 99 71 92 24 49 92 24 49 92 24 49 92 FC 13 99 99 99 99 99 99 71 92 BB 84
20 61 05 24 49 92 24 49 92 24 49 92 FC 13 99 99 99 99 99 99 71 92 24 49 92 24 49 92 24 49 E3 27
1A 61 06 92 FC 13 99 99 99 99 99 99 71 92 24 49 92 24 49 92 24 49 92 FC 1E 32
04 62 29 83

Make SoundOptions use boolean values for its values

SoundOptions currently expects explicit 1 or a 0 as values for hourly_chimes and button_beep. These should be true and false instead.

CPACKET_BEEPS = [0x71]
attr_accessor :hourly_chimes, :button_beep
def initialize(hourly_chimes:, button_beep:)
@hourly_chimes = hourly_chimes
@button_beep = button_beep
end
def packets
[
CPACKET_BEEPS + [hourly_chimes, button_beep]
]
end

Allow length to be passed to paginate_cpackets helper method

While working on #57, I discovered that the Timex Datalink software paginates at 27 bytes for protocol 1 EEPROM data. The paginate_cpackets helper method from the CpacketPaginator module is hard-coded to paginate at 32 bytes:

module CpacketPaginator
ITEMS_PER_PACKET = 32
def paginate_cpackets(header:, cpackets:)
paginated_cpackets = cpackets.each_slice(ITEMS_PER_PACKET)
paginated_cpackets.map.with_index(1) do |paginated_cpacket, index|
header + [index] + paginated_cpacket
end
end
end

Add packet length parameter to this helper method so it can be consumed in the models its shipped with now, and also be used with future protocol 1 EEPROM models.

Implement a more intuitive UX for date formats

This is Time for protocol 3:

# Create a Time instance.
#
# @param zone [Integer] Time zone number (1 or 2).
# @param is_24h [Boolean] Toggle 24 hour time.
# @param date_format [Integer] Date format.
# @param time [::Time] Time to set (including time zone).
# @param name [String, nil] Name of time zone (defaults to zone from time; 3 chars max)
# @return [Time] Time instance.
def initialize(zone:, is_24h:, date_format:, time:, name: nil)
@zone = zone
@is_24h = is_24h
@date_format = date_format
@time = time
@name = name
end

It accepts an integer as a date_format param, but the values of this parameter is undocumented.

Until this gets added to the code, these are the date formats that the date_format values produce on the Datalink 150. The "C" in the values are correct: these formats actually make a "C" displayed in place of the date on the watch. I'm not sure what this is for, so I'm documenting it as-is.

date_format format
0 %_m-%d-%y
1 %_d-%m-%y
2 %y-%m-%d
3 C-%m-%d
4 %_m.%d.%y
5 %_d.%m.%y
6 %y.%m.%d
7 C.%m.%d

Update documentation and possibly add some code to make it more clear what Time's date_format param does.

Make WristApp accept raw program data

WristApp currently expects the entire ZAP file data to be issued. This file follows a little "package" format that has an app name, description, etc., which is read by the original Timex Datalink software and presented to the user:

TDL0724962¬ Applet file header
Example ZAP app¬ Applet friendly name
EXAMPLE18¬ Applet version #
Example ZAP app description
Example of multiple lines in description¬
EXAMPLE.HLP¬ Applet help filename
420¬
none¬ Applet's parent's app name (if it exists - 'none' if it doesn't)
Timex Data Link 150 Watch¬
4C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E73656374657475722061646970697363696E6720656C69742C2073656420646F20656975736D6F642074656D706F7220696E6369646964756E74207574206C61626F726520657420646F6C6F7265206D61676E6120616C697175612
53294¬
0¬ No data
Example ZAP app¬ Applet friendly name
EXAMPLE28¬ Applet version #
Example ZAP app description
Example of multiple lines in description¬
EXAMPLE.HLP¬ Applet help filename
420¬
none¬ Applet's parent's app name (if it exists - 'none' if it doesn't)
Timex Data Link 150s Watch¬
4C6F72656D20697073756D20646F6C6F722073697420616D65742C20636F6E73656374657475722061646970697363696E6720656C69742C2073656420646F20656975736D6F642074656D706F7220696E6369646964756E74207574206C61626F726520657420646F6C6F7265206D61676E6120616C697175612
53294¬
0¬ No data

Only the binary data in this file is sent to the watch, though. For ease of development of WristApps, it would be useful to issue the raw binary data to the WristApp model instead of wrapping everything in the ZAP format.

Description in protocol 3 Time spec doesn't match test data

Here's the spec:

context "when time is 2015-10-21 19:28:32 NZDT" do
let(:tzinfo) { TZInfo::Timezone.get("Pacific/Auckland") }
let(:time) { tzinfo.local_time(1997, 9, 19, 19, 36, 55) }
it_behaves_like "CRC-wrapped packets", [
[0x32, 0x01, 0x37, 0x13, 0x24, 0x09, 0x13, 0x61, 0x17, 0x23, 0x1c, 0x04, 0x01, 0x00]
]
end

The time value is not 2015-10-21 19:28:32 NZDT, which is what's in the spec description.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.