timex_datalink_client 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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: {}