-
Notifications
You must be signed in to change notification settings - Fork 8
/
protocol.rb
158 lines (120 loc) · 3.81 KB
/
protocol.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
require "serialport"
module Pebble
class Protocol
module Errors
class NotConnected < StandardError; end
class LostConnection < StandardError; end
class MalformedResponse < StandardError; end
end
attr_reader :connected
attr_reader :message_handlers
def self.open(port)
protocol = new(port)
begin
protocol.connect
yield protocol
ensure
protocol.disconnect
end
nil
end
def initialize(port)
@port = port
@connected = false
@send_message_mutex = Mutex.new
@message_handlers = Hash.new { |hash, key| hash[key] = [] }
end
def connect
@serial_port = SerialPort.new(@port, baudrate: 115200)
@serial_port.read_timeout = 500
@connected = true
Pebble.logger.debug "Connected to port #{@port}"
@receive_messages_thread = Thread.new(&method(:receive_messages))
true
end
def disconnect
raise Errors::NotConnected unless @connected
@connected = false
@serial_port.close()
@serial_port = nil
true
end
def listen_for_messages
raise Errors::NotConnected unless @connected
@receive_messages_thread.join
end
def on_receive(endpoint = :any, &handler)
@message_handlers[endpoint] << handler
handler
end
def stop_receiving(*params)
handler = params.pop
endpoint = params.pop || :any
@message_handlers[endpoint].delete(handler)
end
def send_message(endpoint, message, async_response_handler = nil, &response_parser)
raise Errors::NotConnected unless @connected
message ||= ""
Pebble.logger.debug "Sending #{Endpoints.for_code(endpoint) || endpoint}: #{message.inspect}"
data = [message.size, endpoint].pack("S>S>") + message
@send_message_mutex.synchronize do
@serial_port.write(data)
if response_parser
if async_response_handler
identifier = on_receive(endpoint) do |response_message|
stop_receiving(endpoint, identifier)
parsed_response = response_parser.call(response_message)
async_response_handler.call(parsed_response)
end
true
else
received = false
parsed_response = nil
identifier = on_receive(endpoint) do |response_message|
stop_receiving(endpoint, identifier)
parsed_response = response_parser.call(response_message)
received = true
end
sleep 0.015 until received
parsed_response
end
else
true
end
end
end
private
def receive_messages
Pebble.logger.debug "Waiting for messages"
while @connected
header = @serial_port.read(4)
next unless header
raise Errors::MalformedResponse if header.length < 4
size, endpoint = header.unpack("S>S>")
message = @serial_port.read(size)
Pebble.logger.debug "Received #{Endpoints.for_code(endpoint) || endpoint}: #{message.inspect}"
trigger_received(endpoint, message)
end
rescue IOError => e
if @connected
Pebble.logger.debug "Lost connection"
@connected = false
raise Errors::LostConnection
end
ensure
Pebble.logger.debug "Finished waiting for messages"
end
def trigger_received(endpoint, message)
@message_handlers[:any].each do |handler|
Thread.new(handler) do |handler|
handler.call(endpoint, message)
end
end
@message_handlers[endpoint].each do |handler|
Thread.new(handler) do |handler|
handler.call(message)
end
end
end
end
end