timex_datalink_client 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f9e485933a8ebd16d37042e129d57ad2aef2814fc6b7c52c5a4a35230b7a2313
4
- data.tar.gz: 393b34c750c6861cffcba17050089f6afcf55f40ce7dae8613fbcd5500c22c16
3
+ metadata.gz: 93fe636dc699046ea5c14683b355f8c65bb162fb18ee91b3ffe27f4a72633808
4
+ data.tar.gz: d9476317e2cce78a77247c277e77208969f6e8fa13059a2e0f7452f502f9b112
5
5
  SHA512:
6
- metadata.gz: 2175e0d2ef370e68ed0a89d9d76d28b2c17503e13ddcd155f070ceac5e4cfab4f5d20850878f6ba95e981865584cf44d9ba3e42dd4e431a48b06ebc9ddfbf35d
7
- data.tar.gz: 28452c2ad771e7649870efff7136340f6699ee38877d8ff9085d5d26f1c4d5d19db96f4cfdeddd33c152fefd5a9474025a147f59d07ba0c012168b3b1f4744e1
6
+ metadata.gz: 313b6bc8ea78a28ee55534ff28648a6544a8aa7dcf1abd731461fc7b9c21ba3711d441de603d7f538666541d5fce4ca0d6206449a2b8cee6cf5d56a263f8691a
7
+ data.tar.gz: df31c376348f69fd05ba4c0de720e443074f68d63788601610021e0328598af3cfa8a3f85789336d20e80aac90be5ac32526bc6872d208afdd2c6965582f1617
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TimexDatalinkClient
4
+ class Helpers
5
+ module FourByteFormatter
6
+ BYTE_NULL = 0x000
7
+ BYTE_TERMINATOR_ENDF = 0x3fe
8
+ BYTE_TERMINATOR_ENDR = 0x3ff
9
+
10
+ def four_byte_format_for(byte_arrays)
11
+ byte_arrays.each_with_index.flat_map do |bytes, bytes_index|
12
+ last_index = bytes_index == byte_arrays.count - 1
13
+ terminator = last_index ? BYTE_TERMINATOR_ENDR : BYTE_TERMINATOR_ENDF
14
+
15
+ bytes_with_terminator = bytes + [terminator]
16
+
17
+ bytes_with_terminator.each_slice(4).flat_map do |bytes_slice|
18
+ bytes_slice.fill(BYTE_NULL, bytes_slice.count, 4 - bytes_slice.count)
19
+
20
+ packet_lsbs_sum = bytes_slice.each_with_index.sum { |byte, index| byte / 256 << 6 - index * 2 }
21
+ packet_msbs = bytes_slice.map { |byte| byte % 256 }
22
+
23
+ [packet_lsbs_sum] + packet_msbs
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ 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
@@ -0,0 +1,228 @@
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 Speech
9
+ include Helpers::FourByteFormatter
10
+
11
+ NICKNAME_LENGTH_WITHOUT_DEVICE = 10
12
+ NICKNAME_LENGTH_WITH_DEVICE = 14
13
+
14
+ NICKNAME_SUFFIXES = [
15
+ [0x353, 0x3fd, 0x04d, 0x003, 0x28d],
16
+ [0x353, 0x3fd, 0x04d, 0x003, 0x27b],
17
+ [0x3fb, 0x363, 0x039, 0x03c],
18
+ [0x3fb, 0x361, 0x039, 0x03c, 0x194, 0x3fd, 0x04b, 0x003, 0x144, 0x327],
19
+ [0x3fb, 0x1ae, 0x030, 0x329, 0x03c, 0x3fb, 0x030, 0x320, 0x03c, 0x039, 0x124],
20
+ [0x3fb, 0x353, 0x003, 0x1ae, 0x2e6, 0x18e],
21
+ [0x361, 0x039, 0x03c, 0x144, 0x3fd, 0x04b],
22
+ [0x3fb, 0x361, 0x33e],
23
+ [0x1cb, 0x039, 0x03c, 0x144, 0x3fd, 0x04b],
24
+ [0x039, 0x35a, 0x1ae, 0x3fd, 0x04b, 0x18e, 0x381],
25
+ [0x039, 0x35a, 0x31c, 0x381],
26
+ [0x353, 0x3fd, 0x04d, 0x003, 0x28d, 0x07b, 0x094],
27
+ [0x1e0, 0x1ab],
28
+ [0x253, 0x3fd, 0x04d, 0x182],
29
+ [0x353, 0x3fd, 0x04d, 0x003, 0x357, 0x10c, 0x3fd, 0x04d]
30
+ ]
31
+
32
+ HEADER_VALUE_1_BASE = 0x0b
33
+ HEADER_VALUE_1_DEVICE_NICK = 4
34
+
35
+ HEADER_VALUE_2_BASE = 0x00
36
+ HEADER_VALUE_2_PHRASES = 26
37
+ HEADER_VALUE_2_DEVICE_NICK = 8
38
+
39
+ HEADER_VALUE_3_BASE = 0x1a
40
+ HEADER_VALUE_3_PHRASES = 2
41
+ HEADER_VALUE_3_DEVICE_NICK = 8
42
+
43
+ HEADER_VALUE_4_DEVICE_BASE = 8
44
+ HEADER_VALUE_4_PHRASE = 2
45
+
46
+ HEADER_VALUE_4_BASES = [0x1a, 0x1a, 0x1a, 0x1f, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a]
47
+ HEADER_VALUE_4_DEVICE_MULTIPLIERS = [5, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 5, 5, 5]
48
+
49
+ HEADER_VALUE_4_DEVICE_INDEXES = [
50
+ [0],
51
+ [0],
52
+ [0],
53
+ [0],
54
+ [0],
55
+ [0],
56
+ [0],
57
+ [0],
58
+ [0],
59
+ [0],
60
+ [0],
61
+ [0, 1, 11],
62
+ [0, 1, 11, 12],
63
+ [0, 1, 11, 12, 13]
64
+ ]
65
+
66
+ HEADER_VALUE_4_USER_MULTIPLIERS = [0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
67
+
68
+ HEADER_VALUE_4_USER_INDEXES = [
69
+ [],
70
+ [],
71
+ [2],
72
+ [2, 5],
73
+ [3, 4, 10],
74
+ [3, 4, 5, 10],
75
+ [3, 4, 5, 6, 10],
76
+ [3, 4, 5, 6, 7, 10],
77
+ [3, 4, 5, 6, 7, 8, 10],
78
+ [3, 4, 5, 6, 7, 8, 9, 10],
79
+ [2, 3, 4, 5, 6, 7, 8, 9, 10],
80
+ [2, 3, 4, 5, 6, 7, 8, 9, 10],
81
+ [2, 3, 4, 5, 6, 7, 8, 9, 10],
82
+ [2, 3, 4, 5, 6, 7, 8, 9, 10]
83
+ ]
84
+
85
+ HEADER_VALUE_5_BASE = 0x8d
86
+
87
+ HEADER_VALUE_5_DEVICE_BASE = -107
88
+ HEADER_VALUE_5_DEVICE_INDEXES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
89
+ HEADER_VALUE_5_DEVICE_MULTIPLIER = 5
90
+
91
+ HEADER_VALUE_5_USER_BASE = 0
92
+ HEADER_VALUE_5_USER_INDEXES = [14]
93
+ HEADER_VALUE_5_USER_MULTIPLIER = 5
94
+
95
+ HEADER_VALUE_5_PHRASE = 2
96
+ HEADER_VALUE_5_PHRASE_PACKET = 5
97
+
98
+ PACKETS_TERMINATOR = 0x05
99
+
100
+ attr_accessor :phrases, :device_nickname, :user_nickname
101
+
102
+ # Create a Speech instance.
103
+ #
104
+ # @param phrases [Array<Array<Integer>>] Two-dimensional array of phrases.
105
+ # @param device_nickname [Array<Integer>] Device nickname.
106
+ # @param user_nickname [Array<Integer>] User nickname.
107
+ # @return [Speech] Speech instance.
108
+ def initialize(phrases: [], device_nickname: [], user_nickname: [])
109
+ @phrases = phrases
110
+ @device_nickname = device_nickname
111
+ @user_nickname = user_nickname
112
+ end
113
+
114
+ # Compile data for nicknames and phrases.
115
+ #
116
+ # @return [Array<Integer>] Compiled data of all nicknames and phrases.
117
+ def packet
118
+ header + nickname_bytes + formatted_phrases + [PACKETS_TERMINATOR]
119
+ end
120
+
121
+ private
122
+
123
+ def header
124
+ all_values = [header_value_1, header_value_2, header_value_3] + header_values_4 + header_values_5
125
+
126
+ all_values.flat_map { |value| value.divmod(256).reverse }
127
+ end
128
+
129
+ def header_value_1
130
+ value_1 = HEADER_VALUE_1_BASE + phrases.count
131
+ value_1 += HEADER_VALUE_1_DEVICE_NICK if device_nickname.any?
132
+
133
+ value_1
134
+ end
135
+
136
+ def header_value_2
137
+ value_2 = HEADER_VALUE_2_BASE
138
+ value_2 += HEADER_VALUE_2_PHRASES if phrases.any?
139
+ value_2 += HEADER_VALUE_2_DEVICE_NICK if phrases.any? && device_nickname.any?
140
+
141
+ value_2
142
+ end
143
+
144
+ def header_value_3
145
+ value_3 = HEADER_VALUE_3_BASE
146
+ value_3 += HEADER_VALUE_3_DEVICE_NICK if device_nickname.any?
147
+ value_3 += HEADER_VALUE_3_PHRASES * phrases.count
148
+
149
+ value_3
150
+ end
151
+
152
+ def header_values_4
153
+ value_4_length = device_nickname.any? ? NICKNAME_LENGTH_WITH_DEVICE : NICKNAME_LENGTH_WITHOUT_DEVICE
154
+
155
+ value_4_length.times.flat_map do |value_4_index|
156
+ device_value = HEADER_VALUE_4_DEVICE_INDEXES[value_4_index].sum { |device_index| packet_lengths[device_index] }
157
+ device_value *= HEADER_VALUE_4_DEVICE_MULTIPLIERS[value_4_index]
158
+
159
+ user_value = HEADER_VALUE_4_USER_INDEXES[value_4_index].sum { |device_index| packet_lengths[device_index] }
160
+ user_value *= HEADER_VALUE_4_USER_MULTIPLIERS[value_4_index]
161
+
162
+ value = device_value + user_value
163
+ value += HEADER_VALUE_4_BASES[value_4_index]
164
+ value += HEADER_VALUE_4_PHRASE * phrases.count
165
+ value += HEADER_VALUE_4_DEVICE_BASE if device_nickname.any?
166
+
167
+ value
168
+ end
169
+ end
170
+
171
+ def header_values_5
172
+ phrases.each_index.flat_map do |phrase_index|
173
+ value = HEADER_VALUE_5_BASE
174
+
175
+ if device_nickname.any?
176
+ device_value = HEADER_VALUE_5_DEVICE_INDEXES.sum { |device_index| packet_lengths[device_index] }
177
+ device_value *= HEADER_VALUE_5_DEVICE_MULTIPLIER
178
+ device_value += HEADER_VALUE_5_DEVICE_BASE
179
+
180
+ value += device_value
181
+ end
182
+
183
+ if user_nickname.any?
184
+ user_value = HEADER_VALUE_5_USER_INDEXES.sum { |device_index| packet_lengths[device_index] }
185
+ user_value *= HEADER_VALUE_5_USER_MULTIPLIER
186
+ user_value += HEADER_VALUE_5_USER_BASE
187
+
188
+ value += user_value
189
+ end
190
+
191
+ value += HEADER_VALUE_5_PHRASE * phrases.count
192
+ value += HEADER_VALUE_5_PHRASE_PACKET * phrases.first(phrase_index).sum { |phrase| 1 + phrase.length / 4 }
193
+
194
+ value
195
+ end
196
+ end
197
+
198
+ def nickname_bytes
199
+ four_byte_format_for(nicknames_with_suffixes)
200
+ end
201
+
202
+ def packet_lengths
203
+ nicknames_with_suffixes.map { |nickname| 1 + nickname.length / 4 }
204
+ end
205
+
206
+ def nickname_format
207
+ format = [device_nickname] * 2
208
+ format += [user_nickname] * 9
209
+ format += [device_nickname] * 4 if device_nickname.any?
210
+
211
+ format
212
+ end
213
+
214
+ def nicknames_with_suffixes
215
+ nickname_format.each_with_index.map do |nickname, nickname_index|
216
+ nickname + NICKNAME_SUFFIXES[nickname_index]
217
+ end
218
+ end
219
+
220
+ def formatted_phrases
221
+ return [] if phrases.empty?
222
+
223
+ four_byte_format_for(phrases)
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,76 @@
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 Protocol7
8
+ class Eeprom
9
+ include Helpers::CpacketPaginator
10
+ prepend Helpers::CrcPacketsWrapper
11
+
12
+ # FIXME use arrays for array models
13
+ # FIXME: check Array<Integer> yardoc around places
14
+
15
+ CPACKET_SECT = [0x90, 0x05]
16
+ CPACKET_DATA = [0x91, 0x05]
17
+ CPACKET_END = [0x92, 0x05]
18
+
19
+ CPACKET_SECT_WELCOME = [
20
+ 0x44, 0x53, 0x49, 0x20, 0x54, 0x6f, 0x79, 0x73, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x2e,
21
+ 0x2e, 0x2e, 0x65, 0x42, 0x72, 0x61, 0x69, 0x6e, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00
22
+ ]
23
+
24
+ CPACKET_DATA_LENGTH = 32
25
+
26
+ attr_accessor :activities, :games, :calendar, :phone_numbers, :speech
27
+
28
+ # Create an Eeprom instance.
29
+ #
30
+ # @param activities [Array<Activity>, nil] Activities to be added to EEPROM data.
31
+ # @param games [Games, nil] Games to be added to EEPROM data.
32
+ # @param calendar [Array<Calendar>, nil] Calendar to be added to EEPROM data.
33
+ # @param phone_numbers [Array<PhoneNumber>, nil] Phone numbers to be added to EEPROM data.
34
+ # @param speech [Speech, nil] Speech to be added to EEPROM data.
35
+ # @return [Eeprom] Eeprom instance.
36
+ def initialize(activities: nil, games: nil, calendar: nil, phone_numbers: nil, speech: nil)
37
+ @activities = activities
38
+ @games = games
39
+ @calendar = calendar
40
+ @phone_numbers = phone_numbers
41
+ @speech = speech
42
+ end
43
+
44
+ # Compile packets for EEPROM data.
45
+ #
46
+ # @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
47
+ def packets
48
+ [header] + payloads + [CPACKET_END]
49
+ end
50
+
51
+ private
52
+
53
+ def header
54
+ [
55
+ CPACKET_SECT,
56
+ payloads.length,
57
+ CPACKET_SECT_WELCOME
58
+ ].flatten
59
+ end
60
+
61
+ def payloads
62
+ paginate_cpackets(header: CPACKET_DATA, length: CPACKET_DATA_LENGTH, cpackets: all_packets)
63
+ end
64
+
65
+ def all_packets
66
+ [].tap do |packets|
67
+ packets.concat(Activity.packets(activities)) if activities
68
+ packets.concat(games.packet) if games
69
+ packets.concat(calendar.packet) if calendar
70
+ packets.concat(PhoneNumber.packets(phone_numbers)) if phone_numbers
71
+ packets.concat(speech.packet) if speech
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/crc_packets_wrapper"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class End
8
+ prepend Helpers::CrcPacketsWrapper
9
+
10
+ CPACKET_SKIP = [0x21]
11
+
12
+ # Compile packets for data end command.
13
+ #
14
+ # @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
15
+ def packets
16
+ [CPACKET_SKIP]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mdb"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class PhraseBuilder
8
+ class WordNotFound < StandardError; end
9
+
10
+ attr_accessor :database
11
+
12
+ # Create a PhraseBuilder instance.
13
+ #
14
+ # @param database [String] Database file to compile phrase data from.
15
+ # @return [PhraseBuilder] PhraseBuilder instance.
16
+ def initialize(database:)
17
+ @database = database
18
+ end
19
+
20
+ # Compile vocabulary IDs for protocol 7 phrases.
21
+ #
22
+ # @param words [Array<String>] Array of words.
23
+ # @raise [WordNotFound] Word not found in protocol 7 database.
24
+ # @return [Array<Integer>] Array of protocol 7 vocabulary IDs.
25
+ def vocab_ids_for(*words)
26
+ words.flat_map do |word|
27
+ vocab = vocab_for_word(word)
28
+
29
+ raise(WordNotFound, "#{word} is not a valid word!") unless vocab
30
+
31
+ vocab_links = vocab_links_for_vocab(vocab)
32
+
33
+ vocab_links.map do |vocab_link|
34
+ linked_vocab = vocab_for_vocab_link(vocab_link)
35
+
36
+ linked_vocab[:"PC Index"].to_i
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def mdb
44
+ @mdb ||= Mdb.open(database)
45
+ end
46
+
47
+ def vocab_table
48
+ @vocab_table ||= mdb["Vocab"]
49
+ end
50
+
51
+ def vocab_links_table
52
+ @vocab_links_table ||= mdb["Vocab Links"]
53
+ end
54
+
55
+ def vocab_for_word(word)
56
+ vocab_table.detect { |vocab| vocab[:Label].casecmp?(word) }
57
+ end
58
+
59
+ def vocab_links_for_vocab(vocab)
60
+ links = vocab_links_table.select { |vocab_link| vocab_link[:"PC Index"] == vocab[:"PC Index"] }
61
+
62
+ links.sort_by { |link| link[:Sequence].to_i }
63
+ end
64
+
65
+ def vocab_for_vocab_link(vocab_link)
66
+ vocab_table.detect { |vocab| vocab[:"PC Index"] == vocab_link[:"eBrain Index"] }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timex_datalink_client/helpers/crc_packets_wrapper"
4
+
5
+ class TimexDatalinkClient
6
+ class Protocol7
7
+ class Start
8
+ prepend Helpers::CrcPacketsWrapper
9
+
10
+ CPACKET_START = [0x20, 0x00, 0x00, 0x07]
11
+
12
+ # Compile packets for data start command.
13
+ #
14
+ # @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
15
+ def packets
16
+ [CPACKET_START]
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TimexDatalinkClient
4
+ class Protocol7
5
+ class Sync
6
+ PING_BYTE = [0x78]
7
+ SYNC_1_BYTE = [0x55]
8
+ SYNC_2_BYTE = [0xaa]
9
+
10
+ SYNC_2_LENGTH = 5
11
+
12
+ attr_accessor :length
13
+
14
+ # Create a Sync instance.
15
+ #
16
+ # @param length [Integer] Number of 0x55 sync bytes to use.
17
+ # @return [Sync] Sync instance.
18
+ def initialize(length: 300)
19
+ @length = length
20
+ end
21
+
22
+ # Compile packets for syncronization data.
23
+ #
24
+ # @return [Array<Array<Integer>>] Two-dimensional array of integers that represent bytes.
25
+ def packets
26
+ [PING_BYTE + render_sync_1 + render_sync_2]
27
+ end
28
+
29
+ private
30
+
31
+ def render_sync_1
32
+ SYNC_1_BYTE * length
33
+ end
34
+
35
+ def render_sync_2
36
+ SYNC_2_BYTE * SYNC_2_LENGTH
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class TimexDatalinkClient
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
@@ -43,6 +43,18 @@ require "timex_datalink_client/protocol_4/sync"
43
43
  require "timex_datalink_client/protocol_4/time"
44
44
  require "timex_datalink_client/protocol_4/wrist_app"
45
45
 
46
+ require "timex_datalink_client/protocol_7/eeprom"
47
+ require "timex_datalink_client/protocol_7/eeprom/activity"
48
+ require "timex_datalink_client/protocol_7/eeprom/calendar"
49
+ require "timex_datalink_client/protocol_7/eeprom/calendar/event"
50
+ require "timex_datalink_client/protocol_7/eeprom/games"
51
+ require "timex_datalink_client/protocol_7/eeprom/phone_number"
52
+ require "timex_datalink_client/protocol_7/eeprom/speech"
53
+ require "timex_datalink_client/protocol_7/end"
54
+ require "timex_datalink_client/protocol_7/phrase_builder"
55
+ require "timex_datalink_client/protocol_7/start"
56
+ require "timex_datalink_client/protocol_7/sync"
57
+
46
58
  require "timex_datalink_client/protocol_9/alarm"
47
59
  require "timex_datalink_client/protocol_9/eeprom"
48
60
  require "timex_datalink_client/protocol_9/eeprom/chrono"
@@ -60,19 +72,20 @@ class TimexDatalinkClient
60
72
 
61
73
  # Create a TimexDatalinkClient instance.
62
74
  #
63
- # @param serial_device [String] Path to serial device.
75
+ # @param serial_device [String, nil] Path to serial device.
64
76
  # @param models [Array<Protocol1::Sync, Protocol1::Start, Protocol1::Time, Protocol1::TimeName, Protocol1::Alarm,
65
77
  # Protocol1::Eeprom, Protocol1::End, Protocol3::Sync, Protocol3::Start, Protocol3::Time, Protocol3::Alarm,
66
78
  # Protocol3::Eeprom, Protocol3::SoundTheme, Protocol3::SoundOptions, Protocol3::WristApp, Protocol3::End,
67
79
  # Protocol4::Sync, Protocol4::Start, Protocol4::Time, Protocol4::Alarm, Protocol4::Eeprom, Protocol4::SoundTheme,
68
- # Protocol4::SoundOptions, Protocol4::WristApp, Protocol4::End, Protocol9::Sync, Protocol9::Start, Protocol9::Time,
69
- # Protocol9::TimeName, Protocol9::Timer, Protocol9::Alarm, Protocol9::Eeprom, Protocol9::SoundOptions,
70
- # Protocol9::End>] Models to compile data for.
80
+ # Protocol4::SoundOptions, Protocol4::WristApp, Protocol4::End, Protocol7::Sync, Protocol7::Start,
81
+ # Protocol7::Eeprom, Protocol7::End, Protocol9::Sync, Protocol9::Start, Protocol9::Time, Protocol9::TimeName,
82
+ # Protocol9::Timer, Protocol9::Alarm, Protocol9::Eeprom, Protocol9::SoundOptions, Protocol9::End>] Models to compile
83
+ # data for.
71
84
  # @param byte_sleep [Integer, nil] Time to sleep after sending byte.
72
85
  # @param packet_sleep [Integer, nil] Time to sleep after sending packet of bytes.
73
86
  # @param verbose [Boolean] Write verbose output to console.
74
87
  # @return [TimexDatalinkClient] TimexDatalinkClient instance.
75
- def initialize(serial_device:, models: [], byte_sleep: nil, packet_sleep: nil, verbose: false)
88
+ def initialize(serial_device: nil, models: [], byte_sleep: nil, packet_sleep: nil, verbose: false)
76
89
  @serial_device = serial_device
77
90
  @models = models
78
91
  @byte_sleep = byte_sleep
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timex_datalink_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maxwell Pray
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-24 00:00:00.000000000 Z
11
+ date: 2022-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: crc
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: mdb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rspec
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -90,6 +104,7 @@ files:
90
104
  - lib/timex_datalink_client/helpers/char_encoders.rb
91
105
  - lib/timex_datalink_client/helpers/cpacket_paginator.rb
92
106
  - lib/timex_datalink_client/helpers/crc_packets_wrapper.rb
107
+ - lib/timex_datalink_client/helpers/four_byte_formatter.rb
93
108
  - lib/timex_datalink_client/helpers/length_packet_wrapper.rb
94
109
  - lib/timex_datalink_client/notebook_adapter.rb
95
110
  - lib/timex_datalink_client/protocol_1/alarm.rb
@@ -129,6 +144,17 @@ files:
129
144
  - lib/timex_datalink_client/protocol_4/sync.rb
130
145
  - lib/timex_datalink_client/protocol_4/time.rb
131
146
  - lib/timex_datalink_client/protocol_4/wrist_app.rb
147
+ - lib/timex_datalink_client/protocol_7/eeprom.rb
148
+ - lib/timex_datalink_client/protocol_7/eeprom/activity.rb
149
+ - lib/timex_datalink_client/protocol_7/eeprom/calendar.rb
150
+ - lib/timex_datalink_client/protocol_7/eeprom/calendar/event.rb
151
+ - lib/timex_datalink_client/protocol_7/eeprom/games.rb
152
+ - lib/timex_datalink_client/protocol_7/eeprom/phone_number.rb
153
+ - lib/timex_datalink_client/protocol_7/eeprom/speech.rb
154
+ - lib/timex_datalink_client/protocol_7/end.rb
155
+ - lib/timex_datalink_client/protocol_7/phrase_builder.rb
156
+ - lib/timex_datalink_client/protocol_7/start.rb
157
+ - lib/timex_datalink_client/protocol_7/sync.rb
132
158
  - lib/timex_datalink_client/protocol_9/alarm.rb
133
159
  - lib/timex_datalink_client/protocol_9/eeprom.rb
134
160
  - lib/timex_datalink_client/protocol_9/eeprom/chrono.rb
@@ -141,7 +167,7 @@ files:
141
167
  - lib/timex_datalink_client/protocol_9/time_name.rb
142
168
  - lib/timex_datalink_client/protocol_9/timer.rb
143
169
  - lib/timex_datalink_client/version.rb
144
- homepage: https://github.com/synthead/timex_datalink_client/tree/v0.7.0
170
+ homepage: https://github.com/synthead/timex_datalink_client/tree/v0.8.0
145
171
  licenses:
146
172
  - MIT
147
173
  metadata: {}