hybridgroup-pebblewatch 0.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 937789bdc1fc2b9c6c8c925996d0167f408ebb60
4
+ data.tar.gz: 3f1f77aa03e41f69d185a7abb47c8fc401dd8a7b
5
+ SHA512:
6
+ metadata.gz: 54c63c16810a10685fb2c9fb424a3c194aa5cb9a53c80ad55d619d900be76a920855e4b3e037d79468017559884c159fc70d18cf18f667fc31e8dfbe33fc9154
7
+ data.tar.gz: b8752582686510075a44f0b4bffe8fd5b4c2b2c0b47c2489915d5650d0668c463ce6b8f05be3b1b6753b3bacee900f3bf01846f20ba1e8e0641fa261c6dee1fa
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Douwe Maan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # pebblewatch
2
+
3
+ A Ruby library for communicating with your Pebble smartwatch.
4
+
5
+ The protocol implementation was based on the documentation at http://pebbledev.org/, the Python implementation at [Hexxeh/libpebble](https://github.com/Hexxeh/libpebble) and the .NET implementation at [barometz/flint](https://github.com/barometz/flint).
6
+
7
+ ## To Do
8
+
9
+ - [x] Basic protocol communication
10
+ - [x] Sending of messages (notifications etc)
11
+ - [x] Receiving of events (log, music control)
12
+ - [ ] Full 2-way app message support
13
+ - [ ] Firmware/app uploading
14
+ - [ ] CLI
15
+ - [ ] REPL
16
+
17
+ ## Installation
18
+
19
+ ```sh
20
+ gem install pebblewatch
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Make sure your Pebble is paired with your computer and set up as a serial port. We're going to need the path or index of the port, which in the case of OS X looks like `/dev/tty.Pebble7F30-SerialPortSe` for Pebble ID `7F30`.
26
+
27
+ ```ruby
28
+ require "pebble"
29
+
30
+ # Create your watch using the serial port assigned to your Pebble.
31
+ watch = Pebble::Watch.new("7F30", "/dev/tty.Pebble7F30-SerialPortSe")
32
+ # You can also use autodetection if you're on OS X:
33
+ # watch = Pebble::Watch.autodetect
34
+
35
+ # The watch object will be on the receiving end of 3 kinds of events:
36
+ watch.on_event(:log) do |event|
37
+ puts "LOG"
38
+ puts "timestamp: #{event.timestamp}"
39
+ puts "level: #{event.level}"
40
+ puts "filename: #{event.filename}"
41
+ puts "linenumber: #{event.linenumber}"
42
+ puts "message: #{event.message}"
43
+ end
44
+
45
+ watch.on_event(:system_message) do |event|
46
+ puts "System Message: #{event.message} (#{event.code})"
47
+ end
48
+
49
+ handler = watch.on_event(:media_control) do |event|
50
+ case event.button
51
+ when :playpause
52
+ puts "Play or pause music"
53
+ when :next
54
+ puts "Next track"
55
+ when :previous
56
+ puts "Previous track"
57
+ end
58
+ end
59
+
60
+ # Suddenly had a change of heart? Just cover your ears.
61
+ watch.stop_listening(:media_control, handler)
62
+
63
+ # To make sure we don't miss any events, we haven't connected yet.
64
+ # Because we also want to *send* stuff to the watch, we will now.
65
+ watch.connect
66
+
67
+
68
+ # We can of course send notifications.
69
+ watch.ping
70
+ watch.notification_sms("Scarlett Johansson", "Hey baby, what are you doing tonight?")
71
+ watch.notification_email("Tim Cook", "RE: Final offer",
72
+ "All right, you drive a hard bargain. We'll pay $2 Billion for the whole shop and that is our final offer.")
73
+
74
+ # Or let Pebble know what we're listening to.
75
+ watch.set_nowplaying_metadata(
76
+ "Artist you've probably never heard of",
77
+ "Album released exclusively through SoundCloud",
78
+ "Song that doesn't suck per se but isn't great either"
79
+ )
80
+
81
+ # We can also do some maintenancy things, although there's currently no firmware/app uploading.
82
+ versions = watch.get_versions
83
+ puts "Normal firmware version: #{versions[:firmwares][:normal][:version]}"
84
+ puts "Recovery firmware version: #{versions[:firmwares][:recovery][:version]}"
85
+ puts "Bluetooth MAC address: #{versions[:btmac]}"
86
+
87
+ # Fun fact: We don't have to wait for the results synchronously. (This works on every message with a response.)
88
+ watch.get_installed_apps do |apps|
89
+ puts "Installed apps: (#{apps[:apps].length} of #{apps[:banks_count]} banks in use)"
90
+ apps[:apps].each do |app|
91
+ puts "#{app[:index]}/#{app[:id]}: #{app[:name]} by #{app[:author]}"
92
+ end
93
+ end
94
+
95
+ # Dieting is not just for dogs anymore.
96
+ watch.remove_app(id, index)
97
+
98
+ time = watch.get_time
99
+ # Or asynchronously: watch.get_time { |time| ... }
100
+
101
+ # Time travel is just one method call away.
102
+ watch.set_time(Time.now + (365 * 24 * 60 * 60))
103
+
104
+ # This is mostly interesting for internal stuff, but since there's a :system_message event as well, I thought why not.
105
+ watch.system_message(Pebble::SystemMessages::FIRMWARE_OUT_OF_DATE)
106
+
107
+ # Yeah, I'd stay away from this one.
108
+ watch.reset
109
+
110
+
111
+ # If you're done sending messages but want the program to keep listing for incoming events, say so:
112
+ watch.listen_for_events
113
+ # Note that listening will only end when the connection is lost, so anything that comes after this call will only then be executed.
114
+ # This will generally be the last call in your program.
115
+
116
+ # If we don't want to wait around and listen, just let the program exit or disconnect explicitly:
117
+ watch.disconnect
118
+ ```
119
+
120
+ Oh, and if you want to do some lower level stuff, have a look at [`Pebble::Protocol`](lib/pebble/protocol.rb) which you can access through `watch.protocol`.
121
+
122
+ ```ruby
123
+ watch.protocol.on_receive(endpoint) do |message|
124
+ # Do whatever
125
+ end
126
+
127
+ watch.protocol.send_message(endpoint, message)
128
+ ```
129
+
130
+ ## Examples
131
+ Check out the [`examples/`](examples) folder for two examples that I actually use myself. They're kind of similar, but should give you an idea of how this whole thing works.
132
+
133
+ ## License
134
+ Copyright (c) 2013 Douwe Maan
135
+
136
+ Permission is hereby granted, free of charge, to any person obtaining
137
+ a copy of this software and associated documentation files (the
138
+ "Software"), to deal in the Software without restriction, including
139
+ without limitation the rights to use, copy, modify, merge, publish,
140
+ distribute, sublicense, and/or sell copies of the Software, and to
141
+ permit persons to whom the Software is furnished to do so, subject to
142
+ the following conditions:
143
+
144
+ The above copyright notice and this permission notice shall be
145
+ included in all copies or substantial portions of the Software.
146
+
147
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
148
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
149
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
150
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
151
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
152
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
153
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ spec = Gem::Specification.load("pebblewatch.gemspec")
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
8
+
9
+ desc "Build the .gem file"
10
+ task :build do
11
+ system "gem build #{spec.name}.gemspec"
12
+ end
13
+
14
+ desc "Push the .gem file to rubygems.org"
15
+ task release: :build do
16
+ system "gem push #{spec.name}-#{spec.version}.gem"
17
+ end
@@ -0,0 +1,26 @@
1
+ module Pebble
2
+ module Capabilities
3
+ module Session
4
+ GAMMA_RAY = 0x80000000
5
+ end
6
+
7
+ module Client
8
+ UNKNOWN = 0
9
+ IOS = 1
10
+ ANDROID = 2
11
+ OSX = 3
12
+ LINUX = 4
13
+ WINDOWS = 5
14
+
15
+ TELEPHONY = 16
16
+ SMS = 32
17
+ GPS = 64
18
+ BTLE = 128
19
+ # CAMERA_FRONT = 240 # Doesn't make sense as it'd mess up the bitmask, but it's apparently true.
20
+ CAMERA_REAR = 256
21
+ ACCEL = 512
22
+ GYRO = 1024
23
+ COMPASS = 2048
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ module Pebble
2
+ module Endpoints
3
+ FIRMWARE = 1
4
+ TIME = 11
5
+ VERSION = 16
6
+ PHONE_VERSION = 17
7
+ SYSTEM_MESSAGE = 18
8
+ MUSIC_CONTROL = 32
9
+ PHONE_CONTROL = 33
10
+ APPLICATION_MESSAGE = 48
11
+ LAUNCHER = 49
12
+ LOGS = 2000
13
+ PING = 2001
14
+ DRAW = 2002
15
+ RESET = 2003
16
+ APP = 2004
17
+ NOTIFICATION = 3000
18
+ RESOURCE = 4000
19
+ SYS_REG = 5000
20
+ FCT_REG = 5001
21
+ APP_INSTALL_MANAGER = 6000
22
+ RUNKEEPER = 7000
23
+ PUT_BYTES = 48879
24
+ MAX_ENDPOINT = 65535
25
+
26
+ def self.for_code(code)
27
+ constants.find { |constant| const_get(constant) == code }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,162 @@
1
+ require "serialport"
2
+
3
+ module Pebble
4
+ class Protocol
5
+ module Errors
6
+ class NotConnected < StandardError; end
7
+ class LostConnection < StandardError; end
8
+ class MalformedResponse < StandardError; end
9
+ end
10
+
11
+ attr_reader :connected
12
+ attr_reader :message_handlers
13
+
14
+ def self.open(port)
15
+ protocol = new(port)
16
+
17
+ begin
18
+ protocol.connect
19
+ yield protocol
20
+ ensure
21
+ protocol.disconnect
22
+ end
23
+ nil
24
+ end
25
+
26
+ def initialize(port)
27
+ @port = port
28
+
29
+ @connected = false
30
+ @send_message_mutex = Mutex.new
31
+ @message_handlers = Hash.new { |hash, key| hash[key] = [] }
32
+ end
33
+
34
+ def connect
35
+ if @port.is_a?(String)
36
+ @serial_port = SerialPort.new(@port, baudrate: 115200)
37
+ @serial_port.read_timeout = 500
38
+ else
39
+ @serial_port = @port
40
+ end
41
+
42
+ @connected = true
43
+ Pebble.logger.debug "Connected to port #{@port}"
44
+
45
+ @receive_messages_thread = Thread.new(&method(:receive_messages))
46
+
47
+ true
48
+ end
49
+
50
+ def disconnect
51
+ raise Errors::NotConnected unless @connected
52
+
53
+ @connected = false
54
+
55
+ @serial_port.close()
56
+ @serial_port = nil
57
+
58
+ true
59
+ end
60
+
61
+ def listen_for_messages
62
+ raise Errors::NotConnected unless @connected
63
+
64
+ @receive_messages_thread.join
65
+ end
66
+
67
+ def on_receive(endpoint = :any, &handler)
68
+ @message_handlers[endpoint] << handler
69
+ handler
70
+ end
71
+
72
+ def stop_receiving(*params)
73
+ handler = params.pop
74
+ endpoint = params.pop || :any
75
+
76
+ @message_handlers[endpoint].delete(handler)
77
+ end
78
+
79
+ def send_message(endpoint, message, async_response_handler = nil, &response_parser)
80
+ raise Errors::NotConnected unless @connected
81
+
82
+ message ||= ""
83
+
84
+ Pebble.logger.debug "Sending #{Endpoints.for_code(endpoint) || endpoint}: #{message.inspect}"
85
+
86
+ data = [message.size, endpoint].pack("S>S>") + message
87
+
88
+ @send_message_mutex.synchronize do
89
+ @serial_port.write(data)
90
+
91
+ if response_parser
92
+ if async_response_handler
93
+ identifier = on_receive(endpoint) do |response_message|
94
+ stop_receiving(endpoint, identifier)
95
+
96
+ parsed_response = response_parser.call(response_message)
97
+
98
+ async_response_handler.call(parsed_response)
99
+ end
100
+
101
+ true
102
+ else
103
+ received = false
104
+ parsed_response = nil
105
+ identifier = on_receive(endpoint) do |response_message|
106
+ stop_receiving(endpoint, identifier)
107
+
108
+ parsed_response = response_parser.call(response_message)
109
+ received = true
110
+ end
111
+
112
+ sleep 0.015 until received
113
+
114
+ parsed_response
115
+ end
116
+ else
117
+ true
118
+ end
119
+ end
120
+ end
121
+
122
+ private
123
+ def receive_messages
124
+ Pebble.logger.debug "Waiting for messages"
125
+ while @connected
126
+ header = @serial_port.read(4)
127
+ next unless header
128
+
129
+ raise Errors::MalformedResponse if header.length < 4
130
+
131
+ size, endpoint = header.unpack("S>S>")
132
+ message = @serial_port.read(size)
133
+
134
+ Pebble.logger.debug "Received #{Endpoints.for_code(endpoint) || endpoint}: #{message.inspect}"
135
+
136
+ trigger_received(endpoint, message)
137
+ end
138
+ rescue IOError => e
139
+ if @connected
140
+ Pebble.logger.debug "Lost connection"
141
+ @connected = false
142
+ raise Errors::LostConnection
143
+ end
144
+ ensure
145
+ Pebble.logger.debug "Finished waiting for messages"
146
+ end
147
+
148
+ def trigger_received(endpoint, message)
149
+ @message_handlers[:any].each do |handler|
150
+ Thread.new(handler) do |handler|
151
+ handler.call(endpoint, message)
152
+ end
153
+ end
154
+
155
+ @message_handlers[endpoint].each do |handler|
156
+ Thread.new(handler) do |handler|
157
+ handler.call(message)
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,16 @@
1
+ module Pebble
2
+ module SystemMessages
3
+ FIRMWARE_AVAILABLE = 0
4
+ FIRMWARE_START = 1
5
+ FIRMWARE_COMPLETE = 2
6
+ FIRMWARE_FAIL = 3
7
+ FIRMWARE_UP_TO_DATE = 4
8
+ FIRMWARE_OUT_OF_DATE = 5
9
+ BLUETOOTH_START_DISCOVERABLE = 6
10
+ BLUETOOTH_END_DISCOVERABLE = 7
11
+
12
+ def self.for_code(code)
13
+ constants.find { |constant| const_get(constant) == code }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Pebble
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,60 @@
1
+ module Pebble
2
+ class Watch
3
+ class AppMessageEvent < Event
4
+ module TupleType
5
+ ByteArray = 0
6
+ String = 1
7
+ UnsignedInteger = 2
8
+ Integer = 3
9
+ end
10
+
11
+ attr_accessor :transaction_id
12
+ attr_accessor :uuid
13
+ attr_accessor :data
14
+
15
+ def self.parse(raw_message)
16
+ return nil if raw_message.length < 19
17
+
18
+ message_type, transaction_id, uuid, dictionary_length = raw_message[0, 19].unpack("CCA16C")
19
+
20
+ data = {}
21
+
22
+ offset = 19
23
+ dictionary_length.times do |i|
24
+ key, tuple_type, item_length = raw_message[offset, 7].unpack("CL>S<")
25
+ offset += 7
26
+
27
+ format = case tuple_type
28
+ when TupleType::ByteArray; "a*"
29
+ when TupleType::String; "A*"
30
+ when TupleType::UnsignedInteger
31
+ case item_length
32
+ when 1; "C"
33
+ when 2; "S<"
34
+ when 4; "L<"
35
+ end
36
+ when TupleType::Integer
37
+ case item_length
38
+ when 1; "c"
39
+ when 2; "s<"
40
+ when 4; "l<"
41
+ end
42
+ end
43
+
44
+ item_data = raw_message[offset, item_length].unpack(format).first
45
+ offset += item_length
46
+
47
+ data[key] = item_data
48
+ end
49
+
50
+ event = new
51
+
52
+ event.transaction_id = transaction_id
53
+ event.uuid = uuid
54
+ event.data = data
55
+
56
+ event
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,14 @@
1
+ module Pebble
2
+ class Watch
3
+ class Event
4
+ def self.parse(message)
5
+ new
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ require "pebble/watch/log_event"
12
+ require "pebble/watch/system_message_event"
13
+ require "pebble/watch/media_control_event"
14
+ require "pebble/watch/app_message_event"
@@ -0,0 +1,41 @@
1
+ module Pebble
2
+ class Watch
3
+ class LogEvent < Event
4
+ attr_accessor :timestamp
5
+ attr_accessor :level
6
+ attr_accessor :filename
7
+ attr_accessor :linenumber
8
+ attr_accessor :message
9
+
10
+ def self.parse(raw_message)
11
+ return nil if raw_message.length < 8
12
+
13
+ timestamp, level, message_size, linenumber = raw_message[0, 8].unpack("L>CCS>")
14
+ filename = raw_message[8, 16]
15
+ message = raw_message[24, message_size]
16
+
17
+ log_levels = {
18
+ 1 => :error,
19
+ 50 => :warning,
20
+ 100 => :info,
21
+ 200 => :debug,
22
+ 250 => :verbose
23
+ }
24
+
25
+ event = new
26
+
27
+ event.timestamp = Time.at(timestamp)
28
+ event.level = log_levels[level] || :unknown
29
+ event.linenumber = linenumber
30
+ event.filename = filename
31
+ event.message = message
32
+
33
+ event
34
+ end
35
+
36
+ def inspect
37
+ "[#{self.level.to_s.capitalize}] #{self.message}"
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ module Pebble
2
+ class Watch
3
+ class MediaControlEvent < Event
4
+ attr_accessor :button
5
+
6
+ BUTTONS = {
7
+ 1 => :playpause,
8
+ 4 => :next,
9
+ 5 => :previous
10
+ }
11
+
12
+ def self.parse(message)
13
+ button_id = message.unpack("C").first
14
+
15
+ return nil unless BUTTONS.has_key?(button_id)
16
+
17
+ event = new
18
+
19
+ event.button = BUTTONS[button_id]
20
+
21
+ event
22
+ end
23
+
24
+ def inspect
25
+ self.button.to_s.capitalize
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ module Pebble
2
+ class Watch
3
+ class SystemMessageEvent < Event
4
+ attr_accessor :code
5
+
6
+ def self.parse(message)
7
+ event = new
8
+
9
+ event.code = message.length == 2 ? message.unpack("S>").first : -1
10
+
11
+ event
12
+ end
13
+
14
+ def message
15
+ SystemMessages.for_code(self.code) || "Unknown"
16
+ end
17
+
18
+ def inspect
19
+ "#{self.message} (#{self.code})"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,279 @@
1
+ require "pebble/watch/event"
2
+
3
+ module Pebble
4
+ class Watch
5
+ module Errors
6
+ class NoWatchesFound < StandardError; end
7
+ end
8
+
9
+ def self.autodetect
10
+ return nil unless RUBY_PLATFORM =~ /darwin/
11
+
12
+ watches = Dir.glob("/dev/tty.Pebble????-SerialPortSe")
13
+
14
+ raise Errors::NoWatchesFound if watches.length == 0
15
+ Pebble.logger.debug "Found multiple watches: #{watches}" if watches.length > 1
16
+
17
+ port = watches.first
18
+ id = port[15, 4]
19
+ Pebble.logger.debug "Detected watch with ID #{id}"
20
+
21
+ return new(id, port)
22
+ end
23
+
24
+ def self.open(id, port)
25
+ watch = new(id, port)
26
+
27
+ begin
28
+ watch.connect
29
+ yield watch
30
+ ensure
31
+ watch.disconnect
32
+ end
33
+ nil
34
+ end
35
+
36
+ attr_reader :id
37
+ attr_reader :protocol
38
+ attr_reader :event_handlers
39
+ attr_accessor :session_capabilities
40
+ attr_accessor :client_capabilities
41
+
42
+ def initialize(id, port)
43
+ @id = id
44
+
45
+ @protocol = Protocol.new(port)
46
+ @event_handlers = Hash.new { |hash, key| hash[key] = [] }
47
+
48
+ # We're mirroring Android here.
49
+ @session_capabilities = Capabilities::Session::GAMMA_RAY
50
+ @client_capabilities = Capabilities::Client::TELEPHONY | Capabilities::Client::SMS | Capabilities::Client::ANDROID
51
+
52
+ answer_phone_version_message
53
+ receive_event_messages
54
+ log_log_events
55
+ end
56
+
57
+ def connect
58
+ @protocol.connect
59
+ end
60
+
61
+ def disconnect
62
+ @protocol.disconnect
63
+ end
64
+
65
+ def listen_for_events
66
+ @protocol.listen_for_messages
67
+ end
68
+
69
+ def on_event(event = :any, &handler)
70
+ @event_handlers[event] << handler
71
+ handler
72
+ end
73
+
74
+ def stop_listening(*params)
75
+ handler = params.pop
76
+ event = params.pop || :any
77
+
78
+ @event_handlers[event].delete(handler)
79
+ end
80
+
81
+
82
+ def ping(cookie = 0xDEADBEEF, &async_response_handler)
83
+ message = [0, cookie].pack("CL>")
84
+
85
+ @protocol.send_message(Endpoints::PING, message, async_response_handler) do |message|
86
+ restype, cookie = message.unpack("CL>")
87
+ cookie
88
+ end
89
+ end
90
+
91
+ def notification_sms(sender, body)
92
+ notification(:sms, sender, body)
93
+ end
94
+
95
+ def notification_email(sender, subject, body)
96
+ notification(:email, sender, body, subject)
97
+ end
98
+
99
+ def set_nowplaying_metadata(artist, album, track)
100
+ message = [16].pack("C")
101
+ message << package_strings(artist, album, track, 30)
102
+
103
+ @protocol.send_message(Endpoints::MUSIC_CONTROL, message)
104
+ end
105
+
106
+ def get_versions(&async_response_handler)
107
+ @protocol.send_message(Endpoints::VERSION, "\x00", async_response_handler) do |message|
108
+ response = {}
109
+
110
+ response[:firmwares] = {}
111
+
112
+ size = 47
113
+ [:normal, :recovery].each_with_index do |type, index|
114
+ offset = index * size + 1
115
+
116
+ fw = {}
117
+
118
+ fw[:timestamp], fw[:version], fw[:commit], fw[:is_recovery],
119
+ fw[:hardware_platform], fw[:metadata_version] =
120
+ message[offset, size].unpack("L>A32A8ccc")
121
+
122
+ fw[:is_recovery] = (fw[:is_recovery] == 1)
123
+
124
+ response[:firmwares][type] = fw
125
+ end
126
+
127
+ response[:bootloader_timestamp], response[:hardware_version], response[:serial] =
128
+ message[95, 25].unpack("L>A9A12")
129
+
130
+ response[:btmac] = message[120, 6].unpack("H*").first.scan(/../).reverse.map { |c| c.upcase }.join(":")
131
+
132
+ response
133
+ end
134
+ end
135
+
136
+ def get_installed_apps(&async_response_handler)
137
+ @protocol.send_message(Endpoints::APP_INSTALL_MANAGER, "\x01", async_response_handler) do |message|
138
+ response = {}
139
+
140
+ response[:banks_count], apps_count = message[1, 8].unpack("L>L>")
141
+ response[:apps] = []
142
+
143
+ size = 78
144
+ apps_count.times do |index|
145
+ offset = index * size + 9
146
+
147
+ app = {}
148
+
149
+ app[:id], app[:index], app[:name], app[:author], app[:flags], app[:version] =
150
+ message[offset, size].unpack("L>L>A32A32L>S>")
151
+
152
+ response[:apps] << app
153
+ end
154
+
155
+ response
156
+ end
157
+ end
158
+
159
+ def remove_app(app_id, app_index)
160
+ message = [2, app_id, app_index].pack("cL>L>")
161
+
162
+ @protocol.send_message(Endpoints::APP_INSTALL_MANAGER, message)
163
+ end
164
+
165
+ def get_time(&async_response_handler)
166
+ @protocol.send_message(Endpoints::TIME, "\x00", async_response_handler) do |message|
167
+ restype, timestamp = message.unpack("CL>")
168
+ Time.at(timestamp)
169
+ end
170
+ end
171
+
172
+ def set_time(time)
173
+ timestamp = time.to_i
174
+ message = [2, timestamp].pack("CL>")
175
+
176
+ @protocol.send_message(Endpoints::TIME, message)
177
+ end
178
+
179
+ def system_message(code)
180
+ Pebble.logger.debug "Sending system message #{SystemMessages.for_code(code)}"
181
+
182
+ message = [code].pack("S>")
183
+
184
+ @protocol.send_message(Endpoints::SYSTEM_MESSAGE, message)
185
+ end
186
+
187
+ def reset
188
+ @protocol.send_message(Endpoints::RESET, "\x00")
189
+ end
190
+
191
+ private
192
+ def answer_phone_version_message
193
+ @protocol.on_receive(Endpoints::PHONE_VERSION) do |message|
194
+ response_message = [1, -1].pack("Cl>")
195
+ response_message << [@session_capabilities, @client_capabilities].pack("L>L>")
196
+
197
+ @protocol.send_message(Endpoints::PHONE_VERSION, response_message)
198
+ end
199
+ end
200
+
201
+ def receive_event_messages
202
+ events = [
203
+ [:log, Endpoints::LOGS, LogEvent],
204
+ [:system_message, Endpoints::SYSTEM_MESSAGE, SystemMessageEvent],
205
+ [:media_control, Endpoints::MUSIC_CONTROL, MediaControlEvent],
206
+ [:app_message, Endpoints::APPLICATION_MESSAGE, AppMessageEvent]
207
+ ]
208
+
209
+ events.each do |(name, endpoint, event_klass)|
210
+ @protocol.on_receive(endpoint) do |message|
211
+ event = event_klass.parse(message)
212
+ trigger_event(name, event) if event
213
+ end
214
+ end
215
+ end
216
+
217
+ def log_log_events
218
+ on_event(:log) do |event|
219
+ case event.level
220
+ when :error
221
+ Pebble.logger.error event.message
222
+ when :warning
223
+ Pebble.logger.warn event.message
224
+ when :info
225
+ Pebble.logger.info event.message
226
+ when :debug, :verbose
227
+ Pebble.logger.debug event.message
228
+ else
229
+ Pebble.logger.info event.message
230
+ end
231
+ end
232
+ end
233
+
234
+ def trigger_event(name, event)
235
+ Pebble.logger.debug "Triggering event '#{name}': #{event.inspect}"
236
+
237
+ @event_handlers[:any].each do |handler|
238
+ Thread.new(handler) do |handler|
239
+ handler.call(name, event)
240
+ end
241
+ end
242
+
243
+ @event_handlers[name].each do |handler|
244
+ Thread.new(handler) do |handler|
245
+ handler.call(event)
246
+ end
247
+ end
248
+ end
249
+
250
+ def notification(type, *params)
251
+ types = {
252
+ email: 0,
253
+ sms: 1
254
+ }
255
+
256
+ timestamp = Time.now.to_i
257
+ params.insert(2, timestamp.to_s)
258
+
259
+ message = [types[type]].pack("C")
260
+ message << package_strings(*params)
261
+
262
+ @protocol.send_message(Endpoints::NOTIFICATION, message)
263
+ end
264
+
265
+ def package_strings(*parts)
266
+ max_part_length = 255
267
+ max_part_length = parts.pop if parts.last.is_a?(Integer)
268
+
269
+ message = ""
270
+ parts.each do |part|
271
+ part ||= ""
272
+
273
+ part = part[0, max_part_length] if part.length > max_part_length
274
+ message << [part.length].pack("C") + part
275
+ end
276
+ message
277
+ end
278
+ end
279
+ end
data/lib/pebble.rb ADDED
@@ -0,0 +1,25 @@
1
+ require "logger"
2
+
3
+ module Pebble
4
+ def self.logger=(new_logger)
5
+ @@logger = new_logger
6
+ end
7
+
8
+ def self.logger
9
+ return @@logger if defined?(@@logger)
10
+ @@logger = default_logger
11
+ end
12
+
13
+ def self.default_logger
14
+ logger = Logger.new(STDOUT)
15
+ logger.level = Logger::INFO
16
+ logger
17
+ end
18
+ end
19
+
20
+ require "pebble/version"
21
+ require "pebble/endpoints"
22
+ require "pebble/capabilities"
23
+ require "pebble/system_messages"
24
+ require "pebble/protocol"
25
+ require "pebble/watch"
@@ -0,0 +1 @@
1
+ require "pebble"
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hybridgroup-pebblewatch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Douwe Maan
8
+ - Ron Evans
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: serialport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '1.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rspec
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ description: A Ruby library for communicating with your Pebble smartwatch.
57
+ email:
58
+ - douwe@selenight.nl
59
+ - artoo@hybridgroup.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - lib/pebblewatch.rb
65
+ - lib/pebble.rb
66
+ - lib/pebble/protocol.rb
67
+ - lib/pebble/capabilities.rb
68
+ - lib/pebble/system_messages.rb
69
+ - lib/pebble/watch.rb
70
+ - lib/pebble/watch/system_message_event.rb
71
+ - lib/pebble/watch/app_message_event.rb
72
+ - lib/pebble/watch/media_control_event.rb
73
+ - lib/pebble/watch/event.rb
74
+ - lib/pebble/watch/log_event.rb
75
+ - lib/pebble/version.rb
76
+ - lib/pebble/endpoints.rb
77
+ - LICENSE
78
+ - README.md
79
+ - Rakefile
80
+ - Gemfile
81
+ homepage: https://github.com/hybridgroup/pebblewatch
82
+ licenses:
83
+ - MIT
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 2.0.3
102
+ signing_key:
103
+ specification_version: 4
104
+ summary: Pebble communication library
105
+ test_files: []