timex_datalink_client 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/lib/timex_datalink_client/helpers/four_byte_formatter.rb +29 -0
  3. data/lib/timex_datalink_client/protocol_4/alarm.rb +59 -0
  4. data/lib/timex_datalink_client/protocol_4/eeprom/anniversary.rb +44 -0
  5. data/lib/timex_datalink_client/protocol_4/eeprom/appointment.rb +49 -0
  6. data/lib/timex_datalink_client/protocol_4/eeprom/list.rb +43 -0
  7. data/lib/timex_datalink_client/protocol_4/eeprom/phone_number.rb +56 -0
  8. data/lib/timex_datalink_client/protocol_4/eeprom.rb +95 -0
  9. data/lib/timex_datalink_client/protocol_4/end.rb +20 -0
  10. data/lib/timex_datalink_client/protocol_4/sound_options.rb +42 -0
  11. data/lib/timex_datalink_client/protocol_4/sound_theme.rb +65 -0
  12. data/lib/timex_datalink_client/protocol_4/start.rb +20 -0
  13. data/lib/timex_datalink_client/protocol_4/sync.rb +40 -0
  14. data/lib/timex_datalink_client/protocol_4/time.rb +77 -0
  15. data/lib/timex_datalink_client/protocol_4/wrist_app.rb +67 -0
  16. data/lib/timex_datalink_client/protocol_7/eeprom/activity.rb +94 -0
  17. data/lib/timex_datalink_client/protocol_7/eeprom/calendar/event.rb +33 -0
  18. data/lib/timex_datalink_client/protocol_7/eeprom/calendar.rb +91 -0
  19. data/lib/timex_datalink_client/protocol_7/eeprom/games.rb +124 -0
  20. data/lib/timex_datalink_client/protocol_7/eeprom/phone_number.rb +79 -0
  21. data/lib/timex_datalink_client/protocol_7/eeprom/speech.rb +228 -0
  22. data/lib/timex_datalink_client/protocol_7/eeprom.rb +76 -0
  23. data/lib/timex_datalink_client/protocol_7/end.rb +20 -0
  24. data/lib/timex_datalink_client/protocol_7/phrase_builder.rb +70 -0
  25. data/lib/timex_datalink_client/protocol_7/start.rb +20 -0
  26. data/lib/timex_datalink_client/protocol_7/sync.rb +40 -0
  27. data/lib/timex_datalink_client/version.rb +1 -1
  28. data/lib/timex_datalink_client.rb +34 -5
  29. metadata +42 -3
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/cpacket_paginator"
4
+ require "timex_datalink_client/helpers/crc_packets_wrapper"
5
+
6
+ class TimexDatalinkClient
7
+ class Protocol4
8
+ class WristApp
9
+ include Helpers::CpacketPaginator
10
+ prepend Helpers::CrcPacketsWrapper
11
+
12
+ CPACKET_CLEAR = [0x93, 0x02]
13
+ CPACKET_SECT = [0x90, 0x02]
14
+ CPACKET_DATA = [0x91, 0x02]
15
+ CPACKET_END = [0x92, 0x02]
16
+
17
+ CPACKET_DATA_LENGTH = 32
18
+ WRIST_APP_DELIMITER = /\xac.*\r\n/n
19
+ WRIST_APP_CODE_INDEX = 18
20
+
21
+ attr_accessor :zap_file
22
+
23
+ # Create a WristApp instance.
24
+ #
25
+ # @param wrist_app_data [String, nil] WristApp data.
26
+ # @param zap_file [String, nil] Path to ZAP file.
27
+ # @return [WristApp] WristApp instance.
28
+ def initialize(wrist_app_data: nil, zap_file: nil)
29
+ @wrist_app_data = wrist_app_data
30
+ @zap_file = zap_file
31
+ end
32
+
33
+ # Compile packets for an alarm.
34
+ #
35
+ # @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
36
+ def packets
37
+ [CPACKET_CLEAR, cpacket_sect] + payloads + [CPACKET_END]
38
+ end
39
+
40
+ private
41
+
42
+ def cpacket_sect
43
+ CPACKET_SECT + [payloads.length, 1]
44
+ end
45
+
46
+ def payloads
47
+ paginate_cpackets(header: CPACKET_DATA, length: CPACKET_DATA_LENGTH, cpackets: wrist_app_data.bytes)
48
+ end
49
+
50
+ def wrist_app_data
51
+ @wrist_app_data || zap_file_data_binary
52
+ end
53
+
54
+ def zap_file_data
55
+ File.open(zap_file, "rb").read
56
+ end
57
+
58
+ def zap_file_data_ascii
59
+ zap_file_data.split(WRIST_APP_DELIMITER)[WRIST_APP_CODE_INDEX]
60
+ end
61
+
62
+ def zap_file_data_binary
63
+ [zap_file_data_ascii].pack("H*")
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/four_byte_formatter"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class Eeprom
8
+ class Activity
9
+ include Helpers::FourByteFormatter
10
+
11
+ METADATA_BYTES_BASE = 6
12
+ METADATA_BYTES_SIZE = 5
13
+
14
+ PACKETS_TERMINATOR = 0x04
15
+
16
+ # Compile data for all activities.
17
+ #
18
+ # @param activities [Array<Activity>] Activities to compile data for.
19
+ # @return [Array] Compiled data of all activities.
20
+ def self.packets(activities)
21
+ header(activities) + metadata_and_messages(activities) + [PACKETS_TERMINATOR]
22
+ end
23
+
24
+ private_class_method def self.header(activities)
25
+ [
26
+ random_speech(activities),
27
+ 0,
28
+ 0,
29
+ 0,
30
+ activities.count,
31
+ 0
32
+ ]
33
+ end
34
+
35
+ private_class_method def self.random_speech(activities)
36
+ activities.each_with_index.sum do |activity, activity_index|
37
+ activity.random_speech ? 1 << activity_index : 0
38
+ end
39
+ end
40
+
41
+ private_class_method def self.metadata_and_messages(activities)
42
+ metadata = activities.each_with_index.map do |activity, activity_index|
43
+ activity.metadata_packet(activities.count + activity_index)
44
+ end
45
+
46
+ messages = activities.map { |activity| activity.messages_packet }
47
+
48
+ (metadata + messages).flatten
49
+ end
50
+
51
+ attr_accessor :time, :messages, :random_speech
52
+
53
+ # Create an Activity instance.
54
+ #
55
+ # @param time [::Time] Time of activity.
56
+ # @param messages [Array<Array<Integer>>] Messages for activity.
57
+ # @param random_speech [Boolean] If activity should have random speech.
58
+ # @return [Activity] Activity instance.
59
+ def initialize(time:, messages:, random_speech:)
60
+ @time = time
61
+ @messages = messages
62
+ @random_speech = random_speech
63
+ end
64
+
65
+ # Compile a metadata packet for an activity.
66
+ #
67
+ # @param activity_index [Integer] Activity index.
68
+ # @return [Array<Integer>] Array of integers that represent bytes.
69
+ def metadata_packet(activity_index)
70
+ [
71
+ time.hour,
72
+ time.min,
73
+ messages.count,
74
+ metadata_bytes(activity_index),
75
+ 0
76
+ ].flatten
77
+ end
78
+
79
+ # Compile a message packet for an activity.
80
+ #
81
+ # @return [Array<Integer>] Array of integers that represent bytes.
82
+ def messages_packet
83
+ four_byte_format_for(messages)
84
+ end
85
+
86
+ private
87
+
88
+ def metadata_bytes(activity_index)
89
+ METADATA_BYTES_BASE + METADATA_BYTES_SIZE * activity_index
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TimexDatalinkClient
4
+ class Protocol7
5
+ class Eeprom
6
+ class Calendar
7
+ class Event
8
+ FIVE_MINUTES_SECONDS = 300
9
+
10
+ attr_accessor :time, :phrase
11
+
12
+ # Create an Event instance.
13
+ #
14
+ # @param time [::Time] Time of event.
15
+ # @param phrase [Array<Integer>] Phrase for event.
16
+ # @return [Event] Event instance.
17
+ def initialize(time:, phrase:)
18
+ @time = time
19
+ @phrase = phrase
20
+ end
21
+
22
+ def time_formatted(device_time)
23
+ device_time_midnight = Time.new(device_time.year, device_time.month, device_time.day)
24
+ seconds = (time - device_time_midnight).to_i
25
+ five_minutes = seconds / FIVE_MINUTES_SECONDS
26
+
27
+ five_minutes.divmod(256).reverse
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/four_byte_formatter"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class Eeprom
8
+ class Calendar
9
+ include Helpers::FourByteFormatter
10
+
11
+ DAY_START_TIME = Time.new(2000)
12
+ DAY_SECONDS = 86400
13
+
14
+ EVENTS_BYTES_BASE = 2
15
+ EVENTS_BYTES_EVENT = 4
16
+ EVENTS_BYTES_PHRASE_PACKET = 5
17
+
18
+ PACKETS_TERMINATOR = 0x01
19
+
20
+ attr_accessor :time, :events
21
+
22
+ # Create a Calendar instance.
23
+ #
24
+ # @param time [::Time] Time to set device to.
25
+ # @param events [Array<Event>] Event instances to add to the calendar.
26
+ # @return [Calendar] Calendar instance.
27
+ def initialize(time:, events: [])
28
+ @time = time
29
+ @events = events
30
+ end
31
+
32
+ # Compile data for calendar.
33
+ #
34
+ # @return [Array<Integer>] Compiled data for calendar.
35
+ def packet
36
+ [
37
+ events_count,
38
+ event_packets,
39
+ event_phrases,
40
+ time.hour,
41
+ time.min,
42
+ days_from_2000,
43
+ time_formatted,
44
+ PACKETS_TERMINATOR
45
+ ].flatten
46
+ end
47
+
48
+ private
49
+
50
+ def events_count
51
+ events.count.divmod(256).reverse
52
+ end
53
+
54
+ def event_packets
55
+ event_bytes = EVENTS_BYTES_BASE
56
+ event_bytes += EVENTS_BYTES_EVENT * events.count
57
+
58
+ [].tap do |event_packets|
59
+ events.each_with_index do |event, event_index|
60
+ event_bytes_formatted = event_bytes.divmod(256).reverse
61
+ event_time_formatted = event.time_formatted(time)
62
+
63
+ event_packets << [event_time_formatted, event_bytes_formatted]
64
+
65
+ event_bytes += EVENTS_BYTES_PHRASE_PACKET * (1 + event.phrase.length / 4)
66
+ end
67
+ end
68
+ end
69
+
70
+ def event_phrases
71
+ phrases = events.map(&:phrase)
72
+
73
+ four_byte_format_for(phrases)
74
+ end
75
+
76
+ def days_from_2000
77
+ since_start_time_seconds = time - DAY_START_TIME
78
+ since_start_time_days = since_start_time_seconds.to_i / DAY_SECONDS
79
+
80
+ since_start_time_days.divmod(256).reverse
81
+ end
82
+
83
+ def time_formatted
84
+ five_mintes = (time.hour * 60 + time.min) / 5
85
+
86
+ five_mintes.divmod(256).reverse
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/four_byte_formatter"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class Eeprom
8
+ class Games
9
+ include Helpers::FourByteFormatter
10
+
11
+ COUNTDOWN_TIMER_SECONDS_DEFAULT = 60
12
+
13
+ COUNTDOWN_TIMER_SOUND_DEFAULT = 0x062
14
+ MUSIC_TIME_KEEPER_SOUND_DEFAULT = 0x062
15
+
16
+ PACKETS_TERMINATOR = 0x02
17
+
18
+ attr_accessor :memory_game_enabled, :fortune_teller_enabled, :countdown_timer_enabled, :countdown_timer_seconds,
19
+ :countdown_timer_sound, :mind_reader_enabled, :music_time_keeper_enabled, :music_time_keeper_sound,
20
+ :morse_code_practice_enabled, :treasure_hunter_enabled, :rhythm_rhyme_buster_enabled, :stop_watch_enabled,
21
+ :red_light_green_light_enabled
22
+
23
+ # Create a Games instance.
24
+ #
25
+ # @param memory_game_enabled [Boolean] Toggle memory game.
26
+ # @param fortune_teller_enabled [Boolean] Toggle fortune teller.
27
+ # @param countdown_timer_enabled [Boolean] Toggle countdown timer.
28
+ # @param countdown_timer_seconds [Integer] Duration for countdown timer in seconds.
29
+ # @param countdown_timer_sound [Integer] Sound for countdown timer.
30
+ # @param mind_reader_enabled [Boolean] Toggle mind reader.
31
+ # @param music_time_keeper_enabled [Boolean] Toggle music time keeper.
32
+ # @param music_time_keeper_sound [Integer] Sound for music time keeper.
33
+ # @param morse_code_practice_enabled [Boolean] Toggle Morse code practice.
34
+ # @param treasure_hunter_enabled [Boolean] Toggle treasure hunter.
35
+ # @param rhythm_rhyme_buster_enabled [Boolean] Toggle rhythm & rhyme buster.
36
+ # @param stop_watch_enabled [Boolean] Toggle stop watch.
37
+ # @param red_light_green_light_enabled [Boolean] Toggle red light, green light.
38
+ # @return [Games] Games instance.
39
+ def initialize(
40
+ memory_game_enabled: false,
41
+ fortune_teller_enabled: false,
42
+ countdown_timer_enabled: false,
43
+ countdown_timer_seconds: COUNTDOWN_TIMER_SECONDS_DEFAULT,
44
+ countdown_timer_sound: COUNTDOWN_TIMER_SOUND_DEFAULT,
45
+ mind_reader_enabled: false,
46
+ music_time_keeper_enabled: false,
47
+ music_time_keeper_sound: MUSIC_TIME_KEEPER_SOUND_DEFAULT,
48
+ morse_code_practice_enabled: false,
49
+ treasure_hunter_enabled: false,
50
+ rhythm_rhyme_buster_enabled: false,
51
+ stop_watch_enabled: false,
52
+ red_light_green_light_enabled: false
53
+ )
54
+ @memory_game_enabled = memory_game_enabled
55
+ @fortune_teller_enabled = fortune_teller_enabled
56
+ @countdown_timer_enabled = countdown_timer_enabled
57
+ @countdown_timer_seconds = countdown_timer_seconds
58
+ @countdown_timer_sound = countdown_timer_sound
59
+ @mind_reader_enabled = mind_reader_enabled
60
+ @music_time_keeper_enabled = music_time_keeper_enabled
61
+ @music_time_keeper_sound = music_time_keeper_sound
62
+ @morse_code_practice_enabled = morse_code_practice_enabled
63
+ @treasure_hunter_enabled = treasure_hunter_enabled
64
+ @rhythm_rhyme_buster_enabled = rhythm_rhyme_buster_enabled
65
+ @stop_watch_enabled = stop_watch_enabled
66
+ @red_light_green_light_enabled = red_light_green_light_enabled
67
+ end
68
+
69
+ # Compile data for games.
70
+ #
71
+ # @return [Array<Integer>] Compiled data for games.
72
+ def packet
73
+ [
74
+ enabled_games,
75
+ countdown_timer_time,
76
+ sounds,
77
+ PACKETS_TERMINATOR
78
+ ].flatten
79
+ end
80
+
81
+ private
82
+
83
+ def enabled_games
84
+ bitmask = games.each_with_index.sum do |game, game_index|
85
+ game ? 1 << game_index : 0
86
+ end
87
+
88
+ bitmask.divmod(256).reverse
89
+ end
90
+
91
+ def countdown_timer_time
92
+ (countdown_timer_seconds * 10).divmod(256).reverse
93
+ end
94
+
95
+ def sounds
96
+ sounds_extra_packet = four_byte_format_for(
97
+ [
98
+ [music_time_keeper_sound],
99
+ [countdown_timer_sound],
100
+ []
101
+ ]
102
+ )
103
+
104
+ sounds_extra_packet.first(10)
105
+ end
106
+
107
+ def games
108
+ [
109
+ memory_game_enabled,
110
+ fortune_teller_enabled,
111
+ countdown_timer_enabled,
112
+ mind_reader_enabled,
113
+ music_time_keeper_enabled,
114
+ morse_code_practice_enabled,
115
+ treasure_hunter_enabled,
116
+ rhythm_rhyme_buster_enabled,
117
+ stop_watch_enabled,
118
+ red_light_green_light_enabled
119
+ ]
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/four_byte_formatter"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class Eeprom
8
+ class PhoneNumber
9
+ include Helpers::FourByteFormatter
10
+
11
+ PHONE_NUMBER_DIGITS_MAP = {
12
+ "0" => 0x001,
13
+ "1" => 0x002,
14
+ "2" => 0x003,
15
+ "3" => 0x004,
16
+ "4" => 0x005,
17
+ "5" => 0x006,
18
+ "6" => 0x007,
19
+ "7" => 0x008,
20
+ "8" => 0x009,
21
+ "9" => 0x00a
22
+ }.freeze
23
+
24
+ PACKETS_TERMINATOR = 0x03
25
+
26
+ # Compile data for all phone numbers.
27
+ #
28
+ # @param phone_numbers [Array<PhoneNumber>] Phone numbers to compile data for.
29
+ # @return [Array] Compiled data of all phone numbers.
30
+ def self.packets(phone_numbers)
31
+ header(phone_numbers) + names_and_numbers(phone_numbers) + [PACKETS_TERMINATOR]
32
+ end
33
+
34
+ private_class_method def self.header(phone_numbers)
35
+ [
36
+ phone_numbers.count,
37
+ 0
38
+ ]
39
+ end
40
+
41
+ private_class_method def self.names_and_numbers(phone_numbers)
42
+ return [] if phone_numbers.empty?
43
+
44
+ names_and_numbers = phone_numbers.flat_map(&:name_and_number)
45
+
46
+ phone_numbers.first.four_byte_format_for(names_and_numbers)
47
+ end
48
+
49
+ attr_accessor :name, :number
50
+
51
+ # Create a PhoneNumber instance.
52
+ #
53
+ # @param name [Array<Integer>] Name associated to phone number.
54
+ # @param number [String] Phone number text.
55
+ # @return [PhoneNumber] PhoneNumber instance.
56
+ def initialize(name: [], number:)
57
+ @name = name
58
+ @number = number
59
+ end
60
+
61
+ # Compile an unformatted name and phone number.
62
+ #
63
+ # @return [Array<Integer>] Array of integers that represent bytes.
64
+ def name_and_number
65
+ [
66
+ name,
67
+ number_characters
68
+ ]
69
+ end
70
+
71
+ private
72
+
73
+ def number_characters
74
+ number.each_char.map { |digit| PHONE_NUMBER_DIGITS_MAP[digit] }
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end