timex_datalink_caldav 0.3.0 → 1.1.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 +4 -4
- data/Gemfile +10 -0
- data/Gemfile.lock +12 -1
- data/LICENSE +21 -0
- data/README.md +37 -14
- data/bin/get_ebrain_db +57 -0
- data/examples/README.md +7 -0
- data/examples/caldav.yaml.sample +8 -0
- data/lib/timex_datalink_caldav/client.rb +170 -54
- data/lib/timex_datalink_caldav/similar_word.rb +88 -0
- data/lib/timex_datalink_caldav/version.rb +1 -1
- data/lib/timex_datalink_caldav.rb +43 -8
- data/timex_datalink_caldav.gemspec +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8301c0dee4e085d6ebbbebbcd41872211625f6798980f688cf66765550519622
|
4
|
+
data.tar.gz: 30171ce1fcdb85fa57b17121982c85ab5805cdbef1f12106d56561397bd82ee6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31a43cf2e995bcd29d51ebae9d3a30002239ac0bea6862f97b0f967daf25734409432e75fbced7727ecc2126e6ec509bdb0c09fd6dc7fcbffed87e9c124d73a5
|
7
|
+
data.tar.gz: 5b8b1f14b62a03c6354c2b555651ae6336b6248ddc633b4749b966929c2e8b6e39c8d6f8845cb57329a46a5dc18c35522bd8a193c115efa781922ead52853705
|
data/Gemfile
CHANGED
@@ -20,3 +20,13 @@ gem "icalendar-recurrence", "~> 1.1"
|
|
20
20
|
gem "activesupport", "~> 7.0"
|
21
21
|
|
22
22
|
gem "tzinfo", "~> 2.0"
|
23
|
+
|
24
|
+
gem "yaml", "~> 0.2.1"
|
25
|
+
|
26
|
+
gem "text", "~> 1.3"
|
27
|
+
|
28
|
+
gem "humanize", "~> 2.5"
|
29
|
+
|
30
|
+
gem "down", "~> 5.4"
|
31
|
+
|
32
|
+
gem "ruby-progressbar", "~> 1.13"
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
timex_datalink_caldav (0.
|
4
|
+
timex_datalink_caldav (1.0.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -24,6 +24,8 @@ GEM
|
|
24
24
|
diff-lcs (1.5.0)
|
25
25
|
domain_name (0.5.20190701)
|
26
26
|
unf (>= 0.0.5, < 1.0.0)
|
27
|
+
down (5.4.1)
|
28
|
+
addressable (~> 2.8)
|
27
29
|
ffi (1.15.5)
|
28
30
|
ffi-compiler (1.0.1)
|
29
31
|
ffi (>= 1.0.0)
|
@@ -36,6 +38,7 @@ GEM
|
|
36
38
|
http-cookie (1.0.5)
|
37
39
|
domain_name (~> 0.5)
|
38
40
|
http-form_data (2.3.0)
|
41
|
+
humanize (2.5.1)
|
39
42
|
i18n (1.13.0)
|
40
43
|
concurrent-ruby (~> 1.0)
|
41
44
|
icalendar (2.8.0)
|
@@ -67,8 +70,10 @@ GEM
|
|
67
70
|
diff-lcs (>= 1.2.0, < 2.0)
|
68
71
|
rspec-support (~> 3.12.0)
|
69
72
|
rspec-support (3.12.0)
|
73
|
+
ruby-progressbar (1.13.0)
|
70
74
|
rubyserial (0.6.0)
|
71
75
|
ffi (~> 1.9, >= 1.9.3)
|
76
|
+
text (1.3.1)
|
72
77
|
timex_datalink_client (0.11.0)
|
73
78
|
activemodel (~> 7.0.4)
|
74
79
|
crc (~> 0.4.2)
|
@@ -79,6 +84,7 @@ GEM
|
|
79
84
|
unf (0.1.4)
|
80
85
|
unf_ext
|
81
86
|
unf_ext (0.0.8.2)
|
87
|
+
yaml (0.2.1)
|
82
88
|
|
83
89
|
PLATFORMS
|
84
90
|
arm64-darwin-22
|
@@ -86,13 +92,18 @@ PLATFORMS
|
|
86
92
|
DEPENDENCIES
|
87
93
|
activesupport (~> 7.0)
|
88
94
|
calendav (~> 0.4.0)
|
95
|
+
down (~> 5.4)
|
96
|
+
humanize (~> 2.5)
|
89
97
|
icalendar (~> 2.8)
|
90
98
|
icalendar-recurrence (~> 1.1)
|
91
99
|
rake (~> 13.0)
|
92
100
|
rspec (~> 3.12)
|
101
|
+
ruby-progressbar (~> 1.13)
|
102
|
+
text (~> 1.3)
|
93
103
|
timex_datalink_caldav!
|
94
104
|
timex_datalink_client (~> 0.11.0)
|
95
105
|
tzinfo (~> 2.0)
|
106
|
+
yaml (~> 0.2.1)
|
96
107
|
|
97
108
|
BUNDLED WITH
|
98
109
|
2.4.10
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Willy Hardy
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# TimexDatalinkCaldav
|
2
2
|
|
3
|
-
TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
|
3
|
+
TimexDatalinkCaldav is a simple Ruby gem designed to sync events from a CalDAV server or an ical formatted ics file to a Timex Datalink watch. It can also be used as a standalone command-line interface (CLI) tool.
|
4
|
+
|
5
|
+
## Pre-requisites
|
6
|
+
|
7
|
+
If you need to install Ruby, follow the Ruby installation instructions [here](https://www.ruby-lang.org/en/documentation/installation/).
|
4
8
|
|
5
9
|
## Installation
|
6
10
|
|
@@ -13,9 +17,7 @@ gem install timex_datalink_caldav
|
|
13
17
|
Or add this line to your application's Gemfile:
|
14
18
|
|
15
19
|
```ruby
|
16
|
-
|
17
|
-
gem "timex_datalink_caldav"
|
18
|
-
end
|
20
|
+
gem "timex_datalink_caldav"
|
19
21
|
```
|
20
22
|
|
21
23
|
And then execute:
|
@@ -33,26 +35,47 @@ Here's an example of how to use the tool in your Ruby code:
|
|
33
35
|
```ruby
|
34
36
|
require 'timex_datalink_caldav'
|
35
37
|
|
36
|
-
client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device)
|
37
|
-
|
38
|
+
client = TimexDatalinkCaldav::Client.new(your_username, your_password, your_server_uri, your_device, your_protocol_version, days_forward)
|
39
|
+
|
40
|
+
client.parse_events
|
41
|
+
client.write_to_watch
|
38
42
|
```
|
39
43
|
|
40
44
|
### As a CLI Tool
|
41
45
|
|
42
|
-
After installing the gem, you can use it as a CLI tool:
|
46
|
+
After installing the gem, you can use it as a CLI tool. You can specify the CalDAV server details directly on the command line:
|
43
47
|
|
44
48
|
```sh
|
45
|
-
timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device
|
49
|
+
timex_datalink_caldav -u https://caldavendpoint.com -n your_username -p your_password -d your_device -a your_protocol_version -f days_forward
|
46
50
|
```
|
47
51
|
|
48
|
-
Please replace `caldavendpoint.com
|
52
|
+
Please replace `https://caldavendpoint.com`, `your_username`, `your_password`, `your_device`, `your_protocol_version`, and `days_forward` with your actual CalDAV server URI, username, password, serial device, protocol version, and number of days to look forward for events, respectively.
|
53
|
+
|
54
|
+
Or you can provide these details in a configuration file:
|
55
|
+
|
56
|
+
```sh
|
57
|
+
timex_datalink_caldav -c config.yml -a 1 -d /dev/tty.usbmodem0000000000001 -f 7
|
58
|
+
```
|
59
|
+
|
60
|
+
The configuration file should be a YAML file in the following format:
|
61
|
+
|
62
|
+
```yaml
|
63
|
+
endpoints:
|
64
|
+
- uri: https://www.google.com/calendar/dav/email@gmail.com/events
|
65
|
+
user: email@gmail.com
|
66
|
+
password: app_password
|
67
|
+
- uri: https://caldavendpoint2.com
|
68
|
+
user: your_username2
|
69
|
+
password: your_password2
|
70
|
+
- uri: https://icalendpoint.com/example.ics
|
71
|
+
```
|
49
72
|
|
50
|
-
The device is a serial device that flashes an led when it receives data. On Linux, this is usually `/dev/
|
73
|
+
The device is a serial device that flashes an led when it receives data. On Linux, this is usually `/dev/tty*`. On macOS, this is usually `$(ls /dev/tty.usbmodem* | head -n 1)`. On Windows, this is usually `COM1`.
|
51
74
|
|
52
|
-
If you want to use this I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter). It is the cheapest and easiest way to get a serial device that works with the Timex Datalink watch.
|
75
|
+
If you want to use this, I highly recommend pairing it with the Raspberry Pi Pico and [this project](https://github.com/famiclone6502/DIY_Datalink_Adapter). It is the cheapest and easiest way to get a serial device that works with the Timex Datalink watch.
|
53
76
|
|
54
|
-
##
|
77
|
+
## Notes
|
55
78
|
|
56
|
-
|
79
|
+
- This gem is not affiliated with Timex, nor is it affiliated with any CalDAV server. It is simply a tool that I wrote to sync my events from my CalDAV server to my Timex Datalink watch.
|
57
80
|
|
58
|
-
|
81
|
+
- This gem uses the anniversary feature for full day events, and the appointments feature for events with a start and end time.
|
data/bin/get_ebrain_db
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'down'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'ruby-progressbar'
|
7
|
+
|
8
|
+
# Define URL and file names
|
9
|
+
url = "https://archive.org/download/ebrain-1.1.6/ebrain-1.1.6.iso"
|
10
|
+
iso_file = File.join(Dir.tmpdir, "ebrain-1.1.6.iso")
|
11
|
+
msi_file = File.join(Dir.tmpdir, "eBrain.MSI")
|
12
|
+
cab_file = File.join(Dir.tmpdir, "Cabs.w4.cab")
|
13
|
+
mdb_file1 = File.join(Dir.tmpdir, "pcvocab.mdb1")
|
14
|
+
mdb_file = "pcvocab.mdb" # This is in the current directory
|
15
|
+
|
16
|
+
# Check if 7zip is installed
|
17
|
+
unless system('which 7z > /dev/null 2>&1')
|
18
|
+
puts "7zip is not installed. Please download and install 7zip from https://www.7-zip.org/download.html"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
# Initialize the progress bar
|
23
|
+
progressbar = nil
|
24
|
+
|
25
|
+
# Download the ISO file
|
26
|
+
puts "Downloading ISO file..."
|
27
|
+
Down.download(url,
|
28
|
+
destination: iso_file,
|
29
|
+
content_length_proc: ->(content_length) {
|
30
|
+
progressbar = ProgressBar.create(
|
31
|
+
title: 'Download Progress',
|
32
|
+
total: content_length,
|
33
|
+
format: '%a %bᗧ%i %p%% %t, Estimated time: %e'
|
34
|
+
)
|
35
|
+
},
|
36
|
+
progress_proc: ->(progress) {
|
37
|
+
progressbar.progress = progress
|
38
|
+
}
|
39
|
+
)
|
40
|
+
|
41
|
+
# Extract the MSI file from the ISO
|
42
|
+
puts "Extracting MSI file..."
|
43
|
+
system("7z e #{iso_file} -o#{File.dirname(msi_file)} #{File.basename(msi_file)}")
|
44
|
+
|
45
|
+
# Extract the CAB file from the MSI
|
46
|
+
puts "Extracting CAB file..."
|
47
|
+
system("7z e #{msi_file} -o#{File.dirname(cab_file)} #{File.basename(cab_file)}")
|
48
|
+
|
49
|
+
# Extract the MDB file from the CAB
|
50
|
+
puts "Extracting MDB file..."
|
51
|
+
system("7z e #{cab_file} -o#{File.dirname(mdb_file1)} #{File.basename(mdb_file1)}")
|
52
|
+
|
53
|
+
# Rename and move the MDB file to the current directory
|
54
|
+
puts "Renaming and moving MDB file..."
|
55
|
+
FileUtils.mv mdb_file1, mdb_file
|
56
|
+
|
57
|
+
puts "Done."
|
data/examples/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# Examples
|
2
|
+
|
3
|
+
This directory contains sample configurations and code for the timex_datalink_caldav gem.
|
4
|
+
|
5
|
+
## Configuration
|
6
|
+
|
7
|
+
`caldav.yaml.sample` is a sample configuration file for CalDAV endpoints. You should rename it to `caldav.yaml` and adjust the URI, username, and password for your endpoints.
|
@@ -1,25 +1,30 @@
|
|
1
1
|
require 'calendav'
|
2
|
+
require 'icalendar'
|
2
3
|
require 'icalendar/recurrence'
|
3
4
|
require 'timex_datalink_client'
|
4
5
|
require 'active_support/time'
|
5
6
|
require 'tzinfo'
|
7
|
+
require 'humanize'
|
8
|
+
require_relative 'similar_word'
|
6
9
|
|
7
10
|
module TimexDatalinkCaldav
|
8
11
|
class Client
|
9
|
-
def initialize(user, password, server_url, serial_device, protocol_version)
|
12
|
+
def initialize(user, password, server_url, serial_device, protocol_version, days_forward = 1)
|
10
13
|
@user = user
|
11
14
|
@password = password
|
12
15
|
@server_url = server_url
|
13
16
|
@serial_device = serial_device
|
17
|
+
@days_forward = days_forward
|
14
18
|
@protocol_version = protocol_version.to_i
|
15
19
|
@protocol_class = case @protocol_version
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
when 1 then TimexDatalinkClient::Protocol1
|
21
|
+
when 3 then TimexDatalinkClient::Protocol3
|
22
|
+
when 4 then TimexDatalinkClient::Protocol4
|
23
|
+
when 7 then TimexDatalinkClient::Protocol7
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Invalid protocol version: #{@protocol_version}"
|
26
|
+
end
|
27
|
+
end
|
23
28
|
|
24
29
|
def get_localzone
|
25
30
|
TZInfo::Timezone.get(TZInfo::Timezone.all_country_zones.detect {|z| z.period_for_local(Time.now).utc_total_offset == Time.now.utc_offset}.identifier)
|
@@ -33,49 +38,140 @@ module TimexDatalinkCaldav
|
|
33
38
|
authentication: :basic_auth
|
34
39
|
)
|
35
40
|
|
36
|
-
|
37
|
-
|
41
|
+
if @user && @password
|
42
|
+
# Create a new client with the credentials
|
43
|
+
client = Calendav.client(credentials)
|
38
44
|
|
39
|
-
|
40
|
-
|
45
|
+
# Get events from the calendar for the next day
|
46
|
+
caldav_events = client.events.list(@server_url, from: Time.now, to: Time.now + @days_forward*24*60*60)
|
47
|
+
events = caldav_events.map { |event| Icalendar::Event.parse(event.calendar_data).first }
|
48
|
+
else
|
49
|
+
cal_file = URI.open(@server_url)
|
50
|
+
cals = Icalendar::Calendar.parse(cal_file)
|
51
|
+
cal = cals.first
|
52
|
+
events = cal.events
|
53
|
+
end
|
54
|
+
|
55
|
+
events
|
41
56
|
end
|
42
57
|
|
43
|
-
def
|
58
|
+
def parse_events
|
44
59
|
events = get_events
|
45
|
-
|
60
|
+
|
46
61
|
appointments = []
|
62
|
+
anniversaries = []
|
47
63
|
appointment_map = {} # Used to avoid duplicate appointments
|
48
|
-
|
64
|
+
anniversary_map = {} # Used to avoid duplicate anniversaries
|
65
|
+
|
66
|
+
phrase_builder = SimlarWord.new(database: "pcvocab.mdb") if @protocol_version == 7
|
67
|
+
|
49
68
|
events.each do |event|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
+
next unless event && event.dtstart && event.dtend
|
70
|
+
|
71
|
+
summary_words = parse_summary(event)
|
72
|
+
|
73
|
+
occurrences = event.occurrences_between(Time.now, Time.now + @days_forward*24*60*60)
|
74
|
+
|
75
|
+
if @protocol_version == 7
|
76
|
+
if all_day_event?(event)
|
77
|
+
add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
|
78
|
+
else
|
79
|
+
add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
if all_day_event?(event)
|
83
|
+
add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
|
84
|
+
else
|
85
|
+
add_appointment(event, appointment_map, appointments, summary_words, occurrences)
|
69
86
|
end
|
70
87
|
end
|
71
88
|
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
|
90
|
+
[appointments, anniversaries]
|
91
|
+
end
|
92
|
+
|
93
|
+
def all_day_event?(event)
|
94
|
+
(event.dtend.to_date - event.dtstart.to_date == 1) && event.dtstart.to_datetime.hour == 0 && event.dtstart.to_datetime.min == 0 && event.dtend.to_datetime.hour == 0 && event.dtend.to_datetime.min == 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def parse_summary(event)
|
98
|
+
event.summary.to_s.split.map do |word|
|
99
|
+
if word =~ /\A[a-zA-Z]+\z/
|
100
|
+
word
|
101
|
+
elsif word =~ /\A\d+\z/
|
102
|
+
word.to_i.humanize
|
103
|
+
end
|
104
|
+
end.compact
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_appointment_event(appointment_map, appointments, phrase_builder, summary_words, occurrences)
|
108
|
+
occurrences.each do |occurrence|
|
109
|
+
est_time = occurrence.start_time.in_time_zone(get_localzone)
|
110
|
+
key = "#{est_time}_#{summary_words}"
|
111
|
+
unless appointment_map[key]
|
112
|
+
puts "Adding appointment event: #{summary_words.join(' ')} at time #{est_time}"
|
113
|
+
event_phrase = phrase_builder.vocab_ids_for(*summary_words)
|
114
|
+
appointment = @protocol_class::Eeprom::Calendar::Event.new(
|
115
|
+
time: est_time,
|
116
|
+
phrase: event_phrase
|
117
|
+
)
|
118
|
+
appointments << appointment
|
119
|
+
appointment_map[key] = true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def add_anniversary_event(anniversary_map, anniversaries, phrase_builder, summary_words, occurrences)
|
125
|
+
occurrences.each do |occurrence|
|
126
|
+
est_time = occurrence.start_time.in_time_zone(get_localzone)
|
127
|
+
key = "#{est_time}_#{summary_words.join(' ')}"
|
128
|
+
unless anniversary_map[key]
|
129
|
+
puts "Adding anniversary event: #{summary_words.join(' ')} at date #{event.dtstart.to_s}"
|
130
|
+
event_phrase = phrase_builder.vocab_ids_for(*summary_words)
|
131
|
+
anniversary = @protocol_class::Eeprom::Calendar::Event.new(
|
132
|
+
time: Time.new(est_time.year, est_time.month, est_time.day, 9, 30, 0),
|
133
|
+
phrase: event_phrase
|
134
|
+
)
|
135
|
+
anniversaries << anniversary
|
136
|
+
anniversary_map[key] = true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def add_appointment(event, appointment_map, appointments, summary_words, occurrences)
|
142
|
+
occurrences.each do |occurrence|
|
143
|
+
est_time = occurrence.start_time.in_time_zone(get_localzone)
|
144
|
+
key = "#{est_time}_#{summary_words}"
|
145
|
+
unless appointment_map[key]
|
146
|
+
puts "Adding appointment: #{summary_words} at time #{est_time}"
|
147
|
+
appointment = @protocol_class::Eeprom::Appointment.new(
|
148
|
+
time: est_time,
|
149
|
+
message: summary_words
|
150
|
+
)
|
151
|
+
appointments << appointment
|
152
|
+
appointment_map[key] = true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def add_anniversary(event, anniversary_map, anniversaries, summary_words, occurrences)
|
158
|
+
occurrences.each do |occurrence|
|
159
|
+
est_time = occurrence.start_time.in_time_zone(get_localzone)
|
160
|
+
key = "#{est_time}_#{summary_words}"
|
161
|
+
unless anniversary_map[key]
|
162
|
+
puts "Adding anniversary: #{summary_words} at date #{event.dtstart.to_s}"
|
163
|
+
anniversary = @protocol_class::Eeprom::Anniversary.new(
|
164
|
+
time: event.dtstart.to_time,
|
165
|
+
anniversary: summary_words
|
166
|
+
)
|
167
|
+
anniversaries << anniversary
|
168
|
+
anniversary_map[key] = true
|
169
|
+
end
|
170
|
+
end
|
76
171
|
end
|
77
172
|
|
78
|
-
def write_to_watch(appointments)
|
173
|
+
def write_to_watch(appointments, anniversaries)
|
174
|
+
appointments.sort_by! { |appointment| appointment.time }
|
79
175
|
# add 3 because it always seems to be about 3 seconds behind.
|
80
176
|
time1 = Time.now + 3
|
81
177
|
time2 = time1.dup.utc
|
@@ -99,7 +195,7 @@ module TimexDatalinkCaldav
|
|
99
195
|
zone: 2,
|
100
196
|
name: time2.zone
|
101
197
|
)
|
102
|
-
|
198
|
+
elsif @protocol_version == 3 || @protocol_version == 4
|
103
199
|
time_model = @protocol_class::Time.new(
|
104
200
|
zone: 1,
|
105
201
|
name: time1.zone,
|
@@ -117,21 +213,41 @@ module TimexDatalinkCaldav
|
|
117
213
|
time_name_model = nil # Not needed for protocol version 3 and 4
|
118
214
|
utc_time_name_model = nil # Not needed for protocol version 3 and 4
|
119
215
|
|
216
|
+
else
|
217
|
+
time_model = nil
|
218
|
+
utc_time_model = nil
|
219
|
+
time_name_model = nil
|
220
|
+
utc_time_name_model = nil
|
221
|
+
end
|
222
|
+
|
223
|
+
if @protocol_version == 7
|
224
|
+
calendar = @protocol_class::Eeprom::Calendar.new(
|
225
|
+
time: time1,
|
226
|
+
events: appointments
|
227
|
+
)
|
228
|
+
models = [
|
229
|
+
@protocol_class::Sync.new,
|
230
|
+
@protocol_class::Start.new,
|
231
|
+
@protocol_class::Eeprom.new(
|
232
|
+
calendar: calendar
|
233
|
+
)
|
234
|
+
]
|
235
|
+
else
|
236
|
+
models = [
|
237
|
+
@protocol_class::Sync.new,
|
238
|
+
@protocol_class::Start.new,
|
239
|
+
time_model,
|
240
|
+
time_name_model,
|
241
|
+
utc_time_model,
|
242
|
+
utc_time_name_model,
|
243
|
+
@protocol_class::Eeprom.new(
|
244
|
+
appointments: appointments,
|
245
|
+
anniversaries: anniversaries,
|
246
|
+
appointment_notification_minutes: 5
|
247
|
+
),
|
248
|
+
@protocol_class::End.new
|
249
|
+
].compact # Remove any nil entries
|
120
250
|
end
|
121
|
-
|
122
|
-
models = [
|
123
|
-
@protocol_class::Sync.new,
|
124
|
-
@protocol_class::Start.new,
|
125
|
-
time_model,
|
126
|
-
time_name_model,
|
127
|
-
utc_time_model,
|
128
|
-
utc_time_name_model,
|
129
|
-
@protocol_class::Eeprom.new(
|
130
|
-
appointments: appointments,
|
131
|
-
appointment_notification_minutes: 5
|
132
|
-
),
|
133
|
-
@protocol_class::End.new
|
134
|
-
].compact # Remove any nil entries
|
135
251
|
|
136
252
|
timex_datalink_client = TimexDatalinkClient.new(
|
137
253
|
serial_device: @serial_device,
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mdb"
|
4
|
+
require "text" # import the text gem
|
5
|
+
|
6
|
+
|
7
|
+
class SimlarWord
|
8
|
+
class WordNotFound < StandardError; end
|
9
|
+
|
10
|
+
attr_accessor :database
|
11
|
+
|
12
|
+
def initialize(database:)
|
13
|
+
@database = database
|
14
|
+
end
|
15
|
+
|
16
|
+
def vocab_ids_for(*words)
|
17
|
+
words.flat_map do |word|
|
18
|
+
vocab = vocab_for_word(word)
|
19
|
+
|
20
|
+
# If the word is not found, look for a similar sounding word
|
21
|
+
if vocab.nil?
|
22
|
+
word = find_similar_word(word)
|
23
|
+
vocab = vocab_for_word(word)
|
24
|
+
end
|
25
|
+
|
26
|
+
raise(WordNotFound, "#{word} is not a valid word!") unless vocab
|
27
|
+
|
28
|
+
vocab_links = vocab_links_for_vocab(vocab)
|
29
|
+
|
30
|
+
vocab_links.map do |vocab_link|
|
31
|
+
linked_vocab = vocab_for_vocab_link(vocab_link)
|
32
|
+
|
33
|
+
linked_vocab[:"PC Index"].to_i
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def find_similar_word(word)
|
41
|
+
word_metaphone = Text::Metaphone.double_metaphone(word)
|
42
|
+
|
43
|
+
# Filter out words that start with '<' before finding the minimum distance
|
44
|
+
filtered_vocab_table = vocab_table.reject { |vocab_word| vocab_word[:Label].start_with?('<') }
|
45
|
+
|
46
|
+
similar_word = filtered_vocab_table.min_by do |vocab_word|
|
47
|
+
distance = Text::Levenshtein.distance(
|
48
|
+
Text::Metaphone.double_metaphone(vocab_word[:Label]).first,
|
49
|
+
word_metaphone.first
|
50
|
+
)
|
51
|
+
distance
|
52
|
+
end
|
53
|
+
|
54
|
+
if similar_word
|
55
|
+
puts "Similar word for '#{word}' is '#{similar_word[:Label]}'"
|
56
|
+
similar_word[:Label]
|
57
|
+
else
|
58
|
+
puts "No similar word found for '#{word}', using original word"
|
59
|
+
word
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def mdb
|
64
|
+
@mdb ||= Mdb.open(database)
|
65
|
+
end
|
66
|
+
|
67
|
+
def vocab_table
|
68
|
+
@vocab_table ||= mdb["Vocab"]
|
69
|
+
end
|
70
|
+
|
71
|
+
def vocab_links_table
|
72
|
+
@vocab_links_table ||= mdb["Vocab Links"]
|
73
|
+
end
|
74
|
+
|
75
|
+
def vocab_for_word(word)
|
76
|
+
vocab_table.detect { |vocab| vocab[:Label].casecmp?(word) }
|
77
|
+
end
|
78
|
+
|
79
|
+
def vocab_links_for_vocab(vocab)
|
80
|
+
links = vocab_links_table.select { |vocab_link| vocab_link[:"PC Index"] == vocab[:"PC Index"] }
|
81
|
+
|
82
|
+
links.sort_by { |link| link[:Sequence].to_i }
|
83
|
+
end
|
84
|
+
|
85
|
+
def vocab_for_vocab_link(vocab_link)
|
86
|
+
vocab_table.detect { |vocab| vocab[:"PC Index"] == vocab_link[:"eBrain Index"] }
|
87
|
+
end
|
88
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
require 'open-uri'
|
3
4
|
require_relative "timex_datalink_caldav/client"
|
4
5
|
|
5
6
|
module TimexDatalinkCaldav
|
@@ -9,27 +10,55 @@ module TimexDatalinkCaldav
|
|
9
10
|
end
|
10
11
|
|
11
12
|
def execute
|
12
|
-
|
13
|
-
|
13
|
+
all_appointments = []
|
14
|
+
all_anniversaries = []
|
15
|
+
|
16
|
+
@options[:endpoints].each do |endpoint|
|
17
|
+
client = TimexDatalinkCaldav::Client.new(endpoint[:user], endpoint[:password], endpoint[:uri], @options[:device], @options[:api], @options[:days_forward])
|
18
|
+
appointments, anniversaries = client.parse_events
|
19
|
+
all_appointments.concat(appointments) if appointments.any?
|
20
|
+
all_anniversaries.concat(anniversaries) if anniversaries.any?
|
21
|
+
end
|
22
|
+
|
23
|
+
if all_appointments.any? || all_anniversaries.any?
|
24
|
+
client = TimexDatalinkCaldav::Client.new(
|
25
|
+
@options[:endpoints][0][:user],
|
26
|
+
@options[:endpoints][0][:password],
|
27
|
+
@options[:endpoints][0][:uri],
|
28
|
+
@options[:device],
|
29
|
+
@options[:api],
|
30
|
+
@options[:days_forward]
|
31
|
+
)
|
32
|
+
client.write_to_watch(all_appointments, all_anniversaries)
|
33
|
+
end
|
14
34
|
end
|
15
35
|
|
16
36
|
private
|
17
37
|
|
18
38
|
def parse_options(arguments)
|
19
|
-
options = {}
|
39
|
+
options = { days_forward: 1 }
|
40
|
+
cli_endpoint = {}
|
20
41
|
OptionParser.new do |opts|
|
21
42
|
opts.banner = "Usage: timex_datalink_caldav [options]"
|
22
43
|
|
44
|
+
opts.on("-c", "--config FILE", "Configuration file") do |v|
|
45
|
+
raise ArgumentError, "Both CLI options and configuration file provided. Please provide only one." if cli_endpoint.any?
|
46
|
+
parsed_config = YAML.load_file(v)
|
47
|
+
options[:endpoints] = parsed_config['endpoints'].map do |endpoint|
|
48
|
+
endpoint.each_with_object({}) { |(k, v), result| result[k.to_sym] = v }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
23
52
|
opts.on("-u", "--uri URI", "CalDAV server URI") do |v|
|
24
|
-
|
53
|
+
cli_endpoint[:uri] = v
|
25
54
|
end
|
26
55
|
|
27
56
|
opts.on("-n", "--user USERNAME", "Username for CalDAV server") do |v|
|
28
|
-
|
57
|
+
cli_endpoint[:user] = v
|
29
58
|
end
|
30
59
|
|
31
60
|
opts.on("-p", "--password PASSWORD", "Password for CalDAV server") do |v|
|
32
|
-
|
61
|
+
cli_endpoint[:password] = v
|
33
62
|
end
|
34
63
|
|
35
64
|
opts.on("-d", "--device DEVICE", "Serial device for Timex Datalink watch") do |v|
|
@@ -39,8 +68,14 @@ module TimexDatalinkCaldav
|
|
39
68
|
opts.on("-a", "--api PROTOCOL_VERSION", "Protocol Version") do |v|
|
40
69
|
options[:api] = v
|
41
70
|
end
|
71
|
+
|
72
|
+
opts.on("-f", "--forward DAYS", Integer, "Number of days to look forward for events") do |v|
|
73
|
+
options[:days_forward] = v
|
74
|
+
end
|
42
75
|
end.parse!(arguments)
|
76
|
+
|
77
|
+
options[:endpoints] = [cli_endpoint] if cli_endpoint.any? and options[:endpoints].nil?
|
43
78
|
options
|
44
79
|
end
|
45
80
|
end
|
46
|
-
end
|
81
|
+
end
|
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
|
|
28
28
|
end
|
29
29
|
spec.require_paths = ["lib"]
|
30
30
|
spec.executables << 'timex_datalink_caldav'
|
31
|
-
|
31
|
+
spec.executables << 'get_ebrain_db'
|
32
32
|
# Uncomment to register a new dependency of your gem
|
33
33
|
# spec.add_dependency "example-gem", "~> 1.0"
|
34
34
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timex_datalink_caldav
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willy Hardy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: 'Adds a CLI and a feature to pull your next day of calendar events into
|
14
14
|
the Timex Datalink watch. Note: Hardcoded protocol1 and EST timezone. At the moment.'
|
@@ -16,17 +16,23 @@ email:
|
|
16
16
|
- zpga8gbp@mailer.me
|
17
17
|
executables:
|
18
18
|
- timex_datalink_caldav
|
19
|
+
- get_ebrain_db
|
19
20
|
extensions: []
|
20
21
|
extra_rdoc_files: []
|
21
22
|
files:
|
22
23
|
- CHANGELOG.md
|
23
24
|
- Gemfile
|
24
25
|
- Gemfile.lock
|
26
|
+
- LICENSE
|
25
27
|
- README.md
|
26
28
|
- Rakefile
|
29
|
+
- bin/get_ebrain_db
|
27
30
|
- bin/timex_datalink_caldav
|
31
|
+
- examples/README.md
|
32
|
+
- examples/caldav.yaml.sample
|
28
33
|
- lib/timex_datalink_caldav.rb
|
29
34
|
- lib/timex_datalink_caldav/client.rb
|
35
|
+
- lib/timex_datalink_caldav/similar_word.rb
|
30
36
|
- lib/timex_datalink_caldav/version.rb
|
31
37
|
- sig/timex_datalink_caldav.rbs
|
32
38
|
- timex_datalink_caldav.gemspec
|
@@ -53,7 +59,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
59
|
- !ruby/object:Gem::Version
|
54
60
|
version: '0'
|
55
61
|
requirements: []
|
56
|
-
rubygems_version: 3.4.
|
62
|
+
rubygems_version: 3.4.13
|
57
63
|
signing_key:
|
58
64
|
specification_version: 4
|
59
65
|
summary: Allows the Timex Datalink watch to sync with a CalDAV server.
|