timex_datalink_client 0.6.0 → 0.8.0

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