maxcube-client 0.4.1 → 0.5.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/.gitignore +6 -0
- data/.rubocop.yml +0 -3
- data/.yardopts +1 -0
- data/README.md +17 -2
- data/bin/maxcube-client +2 -27
- data/doc/MaxCube.html +502 -0
- data/doc/MaxCube/Messages.html +1927 -0
- data/doc/MaxCube/Messages/Handler.html +2912 -0
- data/doc/MaxCube/Messages/InvalidMessage.html +140 -0
- data/doc/MaxCube/Messages/InvalidMessageBody.html +264 -0
- data/doc/MaxCube/Messages/InvalidMessageFormat.html +247 -0
- data/doc/MaxCube/Messages/InvalidMessageLength.html +247 -0
- data/doc/MaxCube/Messages/InvalidMessageType.html +263 -0
- data/doc/MaxCube/Messages/Parser.html +520 -0
- data/doc/MaxCube/Messages/Serializer.html +701 -0
- data/doc/MaxCube/Messages/TCP.html +172 -0
- data/doc/MaxCube/Messages/TCP/Handler.html +1396 -0
- data/doc/MaxCube/Messages/TCP/Parser.html +462 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageA.html +186 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageC.html +1077 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageF.html +206 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageH.html +338 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageL.html +535 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageM.html +510 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageN.html +226 -0
- data/doc/MaxCube/Messages/TCP/Parser/MessageS.html +225 -0
- data/doc/MaxCube/Messages/TCP/Serializer.html +460 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageA.html +186 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageC.html +185 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageF.html +206 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageL.html +185 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageM.html +428 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageN.html +209 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageQ.html +185 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageS.html +1168 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageT.html +240 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageU.html +206 -0
- data/doc/MaxCube/Messages/TCP/Serializer/MessageZ.html +252 -0
- data/doc/MaxCube/Messages/UDP.html +164 -0
- data/doc/MaxCube/Messages/UDP/Handler.html +832 -0
- data/doc/MaxCube/Messages/UDP/Parser.html +609 -0
- data/doc/MaxCube/Messages/UDP/Parser/MessageH.html +218 -0
- data/doc/MaxCube/Messages/UDP/Parser/MessageI.html +215 -0
- data/doc/MaxCube/Messages/UDP/Parser/MessageN.html +226 -0
- data/doc/MaxCube/Messages/UDP/Serializer.html +484 -0
- data/doc/MaxCube/Network.html +167 -0
- data/doc/MaxCube/Network/TCP.html +150 -0
- data/doc/MaxCube/Network/TCP/Client.html +1930 -0
- data/doc/MaxCube/Network/TCP/Client/Commands.html +2457 -0
- data/doc/MaxCube/Network/TCP/SampleServer.html +910 -0
- data/doc/MaxCube/Network/UDP.html +150 -0
- data/doc/MaxCube/Network/UDP/Client.html +518 -0
- data/doc/MaxCube/Network/UDP/SampleSocket.html +628 -0
- data/doc/MaxCube/Runner.html +355 -0
- data/doc/_index.html +518 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +499 -0
- data/doc/file.README.html +140 -0
- data/doc/file_list.html +56 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +140 -0
- data/doc/js/app.js +248 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1699 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/maxcube.rb +11 -0
- data/lib/maxcube/messages.rb +85 -8
- data/lib/maxcube/messages/handler.rb +138 -4
- data/lib/maxcube/messages/parser.rb +33 -2
- data/lib/maxcube/messages/serializer.rb +64 -16
- data/lib/maxcube/messages/tcp.rb +11 -7
- data/lib/maxcube/messages/tcp/handler.rb +50 -2
- data/lib/maxcube/messages/tcp/parser.rb +18 -17
- data/lib/maxcube/messages/tcp/serializer.rb +20 -21
- data/lib/maxcube/messages/tcp/type/a.rb +6 -6
- data/lib/maxcube/messages/tcp/type/c.rb +5 -3
- data/lib/maxcube/messages/tcp/type/f.rb +5 -3
- data/lib/maxcube/messages/tcp/type/h.rb +3 -2
- data/lib/maxcube/messages/tcp/type/l.rb +8 -7
- data/lib/maxcube/messages/tcp/type/m.rb +11 -7
- data/lib/maxcube/messages/tcp/type/n.rb +5 -3
- data/lib/maxcube/messages/tcp/type/q.rb +2 -2
- data/lib/maxcube/messages/tcp/type/s.rb +5 -2
- data/lib/maxcube/messages/tcp/type/t.rb +5 -4
- data/lib/maxcube/messages/tcp/type/u.rb +2 -1
- data/lib/maxcube/messages/tcp/type/z.rb +4 -2
- data/lib/maxcube/messages/udp.rb +7 -0
- data/lib/maxcube/messages/udp/handler.rb +28 -0
- data/lib/maxcube/messages/udp/parser.rb +23 -6
- data/lib/maxcube/messages/udp/serializer.rb +17 -1
- data/lib/maxcube/messages/udp/type/h.rb +2 -0
- data/lib/maxcube/messages/udp/type/i.rb +3 -0
- data/lib/maxcube/messages/udp/type/n.rb +3 -0
- data/lib/maxcube/network.rb +5 -1
- data/lib/maxcube/network/tcp.rb +3 -0
- data/lib/maxcube/network/tcp/client.rb +117 -3
- data/lib/maxcube/network/tcp/client/commands.rb +306 -239
- data/lib/maxcube/network/tcp/sample_server.rb +1 -0
- data/lib/maxcube/network/udp.rb +3 -0
- data/lib/maxcube/network/udp/client.rb +2 -0
- data/lib/maxcube/network/udp/sample_socket.rb +1 -0
- data/lib/maxcube/runner.rb +45 -0
- data/lib/maxcube/version.rb +2 -1
- data/maxcube-client.gemspec +2 -0
- metadata +84 -3
@@ -4,14 +4,27 @@ require 'maxcube/messages/serializer'
|
|
4
4
|
module MaxCube
|
5
5
|
module Messages
|
6
6
|
module UDP
|
7
|
+
# Extends {Messages::Serializer} and {UDP::Handler} of routines
|
8
|
+
# connected to UDP Cube messages serializing.
|
7
9
|
class Serializer
|
8
|
-
include Handler
|
10
|
+
include UDP::Handler
|
9
11
|
include Messages::Serializer
|
10
12
|
|
13
|
+
# Known message types in the direction client -> Cube.
|
11
14
|
MSG_TYPES = %w[I N h c R].freeze
|
12
15
|
|
16
|
+
# {UDP::MSG_PREFIX} with a suffix.
|
13
17
|
MSG_PREFIX = (UDP::MSG_PREFIX + "*\x00").freeze
|
14
18
|
|
19
|
+
# Serializes data from a single hash
|
20
|
+
# into UDP Cube message.
|
21
|
+
# Calls {#check_udp_hash} at the begin
|
22
|
+
# and {#check_udp_msg} at the end.
|
23
|
+
# @param hash [Hash] particular message contents separated into hash.
|
24
|
+
# @option hash [String] :serial_number if not specified,
|
25
|
+
# it is set to universal value.
|
26
|
+
# It is used for broadcast messages.
|
27
|
+
# @return [String] output message.
|
15
28
|
def serialize_udp_hash(hash)
|
16
29
|
check_udp_hash(hash)
|
17
30
|
serial_number = hash[:serial_number] || '*' * 10
|
@@ -21,6 +34,9 @@ module MaxCube
|
|
21
34
|
|
22
35
|
private
|
23
36
|
|
37
|
+
# Tells how to get message type from a message.
|
38
|
+
# @param msg [String] input message.
|
39
|
+
# @return [String] message type.
|
24
40
|
def msg_msg_type(msg)
|
25
41
|
msg[18]
|
26
42
|
end
|
@@ -3,10 +3,13 @@ module MaxCube
|
|
3
3
|
module Messages
|
4
4
|
module UDP
|
5
5
|
class Parser
|
6
|
+
# Get network address message.
|
6
7
|
module MessageN
|
7
8
|
private
|
8
9
|
|
10
|
+
# Local keys without the common ones.
|
9
11
|
N_KEYS = %i[ip_address gateway subnet_mask dns1 dns2].freeze
|
12
|
+
# Mandatory keys.
|
10
13
|
KEYS = (Parser::KEYS + N_KEYS).freeze
|
11
14
|
|
12
15
|
def parse_udp_n(_body)
|
data/lib/maxcube/network.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
require 'socket'
|
2
|
-
require 'thread'
|
3
2
|
|
4
3
|
require 'yaml'
|
5
4
|
|
6
5
|
require 'maxcube'
|
7
6
|
|
8
7
|
module MaxCube
|
8
|
+
# Encapsulates network structures providing clients and servers
|
9
|
+
# that comply Cube messages protocol.
|
10
|
+
# It utilizes parsing and serializing features from {Messages}.
|
9
11
|
module Network
|
12
|
+
# Common localhost IP address.
|
10
13
|
LOCALHOST = 'localhost'.freeze
|
14
|
+
# Common broadcast IP address.
|
11
15
|
BROADCAST = '<broadcast>'.freeze
|
12
16
|
end
|
13
17
|
end
|
data/lib/maxcube/network/tcp.rb
CHANGED
@@ -4,7 +4,10 @@ require 'maxcube/messages/tcp/serializer'
|
|
4
4
|
|
5
5
|
module MaxCube
|
6
6
|
module Network
|
7
|
+
# This module contains classes aimed onto TCP network tools
|
8
|
+
# related to Cube protocol.
|
7
9
|
module TCP
|
10
|
+
# Common port used in Cube TCP communication.
|
8
11
|
PORT = 62_910
|
9
12
|
end
|
10
13
|
end
|
@@ -4,8 +4,37 @@ require_relative 'client/commands'
|
|
4
4
|
module MaxCube
|
5
5
|
module Network
|
6
6
|
module TCP
|
7
|
+
# Fundamental class that provides TCP communication
|
8
|
+
# with Cube gateway and connected devices.
|
9
|
+
# After connecting to Cube ({#connect}),
|
10
|
+
# interactive shell is launched.
|
11
|
+
#
|
12
|
+
# Communication with Cube is performed via messages,
|
13
|
+
# whereas client works with hashes,
|
14
|
+
# which have particular message contents divided
|
15
|
+
# and is human readable.
|
16
|
+
# An issue is how to pass contents of hashes
|
17
|
+
# as arguments of message serialization.
|
18
|
+
# For simple hashes client provides and option
|
19
|
+
# to pass arguments explicitly on command line.
|
20
|
+
# This would be difficult to accomplish
|
21
|
+
# for large hashes with subhashes,
|
22
|
+
# so YAML files are used in these cases,
|
23
|
+
# which are able to be generated both automatically and manually.
|
24
|
+
# This file has to be loaded into internal hash before each such message.
|
25
|
+
#
|
26
|
+
# Client interactive shell contains quite detailed usage message.
|
7
27
|
class Client
|
8
|
-
|
28
|
+
# Default verbose mode on startup.
|
29
|
+
DEFAULT_VERBOSE = true
|
30
|
+
# Default persist mode on startup.
|
31
|
+
DEFAULT_PERSIST = true
|
32
|
+
|
33
|
+
# Creates all necessary internal variables.
|
34
|
+
# Internal hash is invalid on startup.
|
35
|
+
# @param verbose [Boolean] verbose mode on startup.
|
36
|
+
# @param persist [Boolean] persist mode on startup.
|
37
|
+
def initialize(verbose: DEFAULT_VERBOSE, persist: DEFAULT_PERSIST)
|
9
38
|
@parser = Messages::TCP::Parser.new
|
10
39
|
@serializer = Messages::TCP::Serializer.new
|
11
40
|
@queue = Queue.new
|
@@ -22,16 +51,27 @@ module MaxCube
|
|
22
51
|
@load_data_dir = @data_dir + 'load'
|
23
52
|
@save_data_dir = @data_dir + 'save'
|
24
53
|
|
25
|
-
@verbose =
|
26
|
-
@persist =
|
54
|
+
@verbose = verbose
|
55
|
+
@persist = persist
|
27
56
|
end
|
28
57
|
|
58
|
+
# Connects to concrete address and starts interactive shell ({#shell}).
|
59
|
+
# Calls {#receiver} in separate thread to receive all incoming messages.
|
60
|
+
# @param host remote host address.
|
61
|
+
# @param port remote host port.
|
29
62
|
def connect(host = LOCALHOST, port = PORT)
|
30
63
|
@socket = TCPSocket.new(host, port)
|
31
64
|
@thread = Thread.new(self, &:receiver)
|
32
65
|
shell
|
33
66
|
end
|
34
67
|
|
68
|
+
# Routine started in separate thread
|
69
|
+
# that receives and parses all incoming messages in loop
|
70
|
+
# and stores them info thread-safe queue.
|
71
|
+
# Parsing is done via
|
72
|
+
# {Messages::TCP::Parser#parse_tcp_msg}.
|
73
|
+
# It should close gracefully on any +IOError+
|
74
|
+
# or on shell's initiative.
|
35
75
|
def receiver
|
36
76
|
puts '<Starting receiver thread ...>'
|
37
77
|
while (data = @socket.gets)
|
@@ -50,6 +90,17 @@ module MaxCube
|
|
50
90
|
puts e.to_s.capitalize
|
51
91
|
end
|
52
92
|
|
93
|
+
# Interactive shell that maintains all operations with Cube.
|
94
|
+
# It is yet only simple +STDIN+ parser
|
95
|
+
# without any command history and other features
|
96
|
+
# that possess all decent shells.
|
97
|
+
# It calls {#command} on every input.
|
98
|
+
# It provides quite detailed usage message ({#cmd_usage}).
|
99
|
+
#
|
100
|
+
# It should close gracefully
|
101
|
+
# from user's will, when connection closes,
|
102
|
+
# or when soft interrupt appears.
|
103
|
+
# Calls {#close} when closing.
|
53
104
|
def shell
|
54
105
|
puts "Welcome to interactive shell!\n" \
|
55
106
|
"Type 'help' for list of commands.\n\n"
|
@@ -64,6 +115,7 @@ module MaxCube
|
|
64
115
|
close
|
65
116
|
end
|
66
117
|
|
118
|
+
# Closes client gracefully.
|
67
119
|
def close
|
68
120
|
STDIN.close
|
69
121
|
send_msg('q')
|
@@ -73,6 +125,9 @@ module MaxCube
|
|
73
125
|
|
74
126
|
private
|
75
127
|
|
128
|
+
# Moves contents of receiver's queue to internal buffer.
|
129
|
+
# Queue is being filled from {#receiver}.
|
130
|
+
# Operation is thread-safe.
|
76
131
|
def refresh_buffer
|
77
132
|
until @queue.empty?
|
78
133
|
data, hashes = @queue.pop
|
@@ -81,11 +136,22 @@ module MaxCube
|
|
81
136
|
end
|
82
137
|
end
|
83
138
|
|
139
|
+
# Returns only current or all (without or with history)
|
140
|
+
# collected part of buffer and history
|
141
|
+
# (contents of buffer is moved to history on clear command).
|
142
|
+
# @param dir_key [:recv, :sent] received or sent data.
|
143
|
+
# @param data_key [:hashes, :data] hashes or raw data (set of messages).
|
144
|
+
# @param history [Boolean] whether to include history.
|
145
|
+
# @return [Array<Hash>, String] demanded data.
|
84
146
|
def buffer(dir_key, data_key, history = false)
|
85
147
|
return @buffer[dir_key][data_key] unless history
|
86
148
|
@history[dir_key][data_key] + @buffer[dir_key][data_key]
|
87
149
|
end
|
88
150
|
|
151
|
+
# Executes command from shell command line.
|
152
|
+
# It calls a method dynamically according to {COMMANDS},
|
153
|
+
# or displays usage message {#cmd_usage}.
|
154
|
+
# @param line [String] command line from +STDIN+
|
89
155
|
def command(line)
|
90
156
|
cmd, *args = line.chomp.split
|
91
157
|
return nil unless cmd
|
@@ -102,6 +168,19 @@ module MaxCube
|
|
102
168
|
cmd_usage
|
103
169
|
end
|
104
170
|
|
171
|
+
include Commands
|
172
|
+
|
173
|
+
# Zips +args+ with appropriate keys
|
174
|
+
# according to {Messages::Handler#msg_type_hash_keys}
|
175
|
+
# and {Messages::Handler#msg_type_hash_opt_keys}.
|
176
|
+
# @param type [String] message type.
|
177
|
+
# @param args [Array<String>] arguments from command line.
|
178
|
+
# @param opts [Hash] options that modifies interpreting of +args+.
|
179
|
+
# @option opts [Boolean] :last_array whether to insert
|
180
|
+
# all rest arguments into array that will be stored into the last key.
|
181
|
+
# @option opts [Boolean] :array_nonempty whether to require +last_array+
|
182
|
+
# _not_ to be empty.
|
183
|
+
# @return [Hash, nil] resulting hash, or +nil+ on failure.
|
105
184
|
def send_msg_hash_from_keys_args(type, *args, **opts)
|
106
185
|
keys = @serializer.msg_type_hash_keys(type) +
|
107
186
|
@serializer.msg_type_hash_opt_keys(type)
|
@@ -118,18 +197,40 @@ module MaxCube
|
|
118
197
|
keys.zip(args).to_h.reject { |_, v| v.nil? }
|
119
198
|
end
|
120
199
|
|
200
|
+
# Returns hash via {#cmd_load}.
|
201
|
+
# It is used to combine sending a message with loading a hash from file.
|
202
|
+
# On success and in non-persistive mode,
|
203
|
+
# it simultaneously invalidates internal hash flag.
|
204
|
+
# @param args [Array<String>] arguments from command line.
|
205
|
+
# @return [Hash, nil] loaded hash, or +nil+ on failure.
|
121
206
|
def send_msg_hash_from_internal(*args, **_opts)
|
122
207
|
return nil unless cmd_load(*args.drop(1))
|
123
208
|
@hash_set = false unless @persist
|
124
209
|
@hash
|
125
210
|
end
|
126
211
|
|
212
|
+
# Command line token that enables loading arguments (hash) from file.
|
127
213
|
ARGS_FROM_HASH = '-'.freeze
|
128
214
|
|
215
|
+
# @param args [Array<String>] arguments from command line.
|
216
|
+
# @return [Boolean] whether to enable loading arguments (hash)
|
217
|
+
# from file.
|
129
218
|
def args_from_hash?(args)
|
130
219
|
args.first == ARGS_FROM_HASH
|
131
220
|
end
|
132
221
|
|
222
|
+
# Returns hash with contents necessary for serialization
|
223
|
+
# of message of given message type.
|
224
|
+
# It is either built from command line +args+
|
225
|
+
# ({#send_msg_hash_from_keys_args}),
|
226
|
+
# or loaded from YAML file ({#send_msg_hash_from_internal}).
|
227
|
+
# @param type [String] message type.
|
228
|
+
# @param args [Array<String>] arguments from command line.
|
229
|
+
# @param opts [Hash] options that modifies interpreting of +args+.
|
230
|
+
# @option opts [Boolean] :load_only means that hash
|
231
|
+
# must be loaded from file (contents are too complex).
|
232
|
+
# Specifying {ARGS_FROM_HASH} is optional in this case.
|
233
|
+
# @return [Hash] resulting hash.
|
133
234
|
def send_msg_hash(type, *args, **opts)
|
134
235
|
if opts[:load_only] && !args_from_hash?(args)
|
135
236
|
args.unshift(ARGS_FROM_HASH)
|
@@ -143,6 +244,17 @@ module MaxCube
|
|
143
244
|
send_msg_hash_from_keys_args(type, *args, **opts)
|
144
245
|
end
|
145
246
|
|
247
|
+
# Performs message serialization and sends it to Cube.
|
248
|
+
# It builds the hash to serialize from by {#send_msg_hash},
|
249
|
+
# and serializes it with
|
250
|
+
# {Messages::TCP::Serializer#serialize_tcp_hash}.
|
251
|
+
#
|
252
|
+
# Both sent message and built hash are buffered.
|
253
|
+
#
|
254
|
+
# It catches all {Messages::InvalidMessage} exceptions.
|
255
|
+
# @param type [String] message type.
|
256
|
+
# @param args [Array<String>] arguments from command line.
|
257
|
+
# @param opts [Hash] options that modifies interpreting of +args+.
|
146
258
|
def send_msg(type, *args, **opts)
|
147
259
|
hash = send_msg_hash(type, *args, **opts)
|
148
260
|
return unless hash
|
@@ -165,6 +277,8 @@ module MaxCube
|
|
165
277
|
puts e.to_s.capitalize
|
166
278
|
end
|
167
279
|
|
280
|
+
# Prints hash in human readable way.
|
281
|
+
# @param hash [Hash] input hash.
|
168
282
|
def print_hash(hash)
|
169
283
|
puts hash.to_yaml
|
170
284
|
end
|
@@ -3,282 +3,349 @@ module MaxCube
|
|
3
3
|
module Network
|
4
4
|
module TCP
|
5
5
|
class Client
|
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
|
-
usage_line('dump', '',
|
61
|
-
"Shortcut for 'data' + 'clear'") <<
|
62
|
-
usage_line('list', '',
|
63
|
-
'Requests for new list of devices', 'l', 'L') <<
|
64
|
-
usage_line('config', '',
|
65
|
-
'Requests for configuration message', 'c', 'C') <<
|
66
|
-
usage_line('send', '{}',
|
67
|
-
'Sends settings to connected devices',
|
68
|
-
's', 'S') <<
|
69
|
-
usage_line('pair', '{<timeout>}',
|
70
|
-
'Sets device into pairing mode' \
|
71
|
-
" with optional timeout\n" \
|
72
|
-
'(request for a new device)', 'n', 'N') <<
|
73
|
-
usage_line('ntp', '{<NTP servers...>}',
|
74
|
-
'Requests for NTP servers' \
|
75
|
-
' and optionally updates them',
|
76
|
-
'f', 'F') <<
|
77
|
-
usage_line('url', '{<URL> <port>}',
|
78
|
-
'Configures Cube\'s portal URL', 'u') <<
|
79
|
-
usage_line('wake', '{<time> <scope> [<ID>]}',
|
80
|
-
'Wake-ups the Cube',
|
81
|
-
'z', 'A') <<
|
82
|
-
usage_line('metadata', '{}',
|
83
|
-
'Serializes metadata for the Cube',
|
84
|
-
'm', 'M') <<
|
85
|
-
usage_line('delete', '{<count> <force> <RF addresses...>}',
|
86
|
-
'Deletes one or more devices from the Cube (!)',
|
87
|
-
't', 'A') <<
|
88
|
-
usage_line('reset', '',
|
89
|
-
'Requests for factory reset (!)', 'a', 'A') <<
|
90
|
-
usage_line('verbose', '',
|
91
|
-
"Toggles verbose mode (whether is incoming data\n" \
|
92
|
-
'printed immediately or is not printed)') <<
|
93
|
-
usage_line('save', '[a|A|all]',
|
94
|
-
"Saves buffered [all] received and sent data\n" \
|
95
|
-
"into files at '#{@save_data_dir}'") <<
|
96
|
-
usage_line('load', '[<path>]',
|
97
|
-
'Loads first hash from YAML file' \
|
98
|
-
" to internal variable\n" \
|
99
|
-
"-> to pass data with outgoing message\n" \
|
100
|
-
'If path is relative,' \
|
101
|
-
" it looks in '#{@load_data_dir}'\n" \
|
102
|
-
"(loads previous valid hash if no file given)\n" \
|
103
|
-
'(command can be combined' \
|
104
|
-
" using '#{ARGS_FROM_HASH}'\n" \
|
105
|
-
" with other commands which have '{}' arguments)") <<
|
106
|
-
usage_line('persist', '',
|
107
|
-
'Toggles persistent mode' \
|
108
|
-
"(whether is internal hash\n" \
|
109
|
-
'not invalidated after use)') <<
|
110
|
-
usage_line('quit', '',
|
111
|
-
"Shuts the client down gracefully\n" \
|
112
|
-
'(SIGINT and EOF also work)', 'q') <<
|
113
|
-
"\n[<arg>] means optional argument <arg>" \
|
114
|
-
"\n[<args...>] means multiple arguments <args...> or none" \
|
115
|
-
"\n (<args...> requires at least one)" \
|
116
|
-
"\n{<arg>} means that either <arg>" \
|
117
|
-
" or '#{ARGS_FROM_HASH}' is expected" \
|
118
|
-
"\n (when '#{ARGS_FROM_HASH}' specified as first argument," \
|
119
|
-
' internal hash is used' \
|
120
|
-
"\n -> 'load' command is called with rest arguments)" \
|
121
|
-
"\n ({} means that only internal hash can be used," \
|
122
|
-
"\n '#{ARGS_FROM_HASH}' is not necessary in this case)"
|
123
|
-
end
|
6
|
+
# Provides handling of concrete commands from shell command line.
|
7
|
+
module Commands
|
8
|
+
private
|
9
|
+
|
10
|
+
# List of commands and their aliases.
|
11
|
+
COMMANDS = {
|
12
|
+
'usage' => %w[? h help],
|
13
|
+
'data' => %w[B buffer d],
|
14
|
+
'history' => %w[H hist],
|
15
|
+
'clear' => %w[C],
|
16
|
+
'dump' => %w[D],
|
17
|
+
'list' => %w[l],
|
18
|
+
'config' => %w[c],
|
19
|
+
'send' => %w[cmd s set],
|
20
|
+
'pair' => %w[n],
|
21
|
+
'ntp' => %w[N f],
|
22
|
+
'url' => %w[U u],
|
23
|
+
'wake' => %w[w z],
|
24
|
+
'metadata' => %w[m meta],
|
25
|
+
'delete' => %w[del],
|
26
|
+
'reset' => %w[],
|
27
|
+
'verbose' => %w[V],
|
28
|
+
'save' => %w[S],
|
29
|
+
'load' => %w[L],
|
30
|
+
'persist' => %w[P],
|
31
|
+
'quit' => %w[q],
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
# Returns usage message for a single command.
|
35
|
+
# @param command [String] command key from {COMMANDS}.
|
36
|
+
# @param args [String] arguments accepted by command.
|
37
|
+
# @param description [String] description of command.
|
38
|
+
# @param message [String, nil] optional message type
|
39
|
+
# that is about to be sent by the command.
|
40
|
+
# @param response [String, nil] optional message type
|
41
|
+
# of expected response to this message.
|
42
|
+
# @return [String] usage message for +command+.
|
43
|
+
def usage_cmd(command, args, description,
|
44
|
+
message = nil, response = nil)
|
45
|
+
cmds_str = (COMMANDS[command].dup << command).join('|')
|
46
|
+
cmds_str << ' ' << args unless args.empty?
|
47
|
+
|
48
|
+
description, *rest = description.split("\n")
|
49
|
+
rest << "[#{message} message]" if message
|
50
|
+
rest << "[#{response} response]" if response
|
51
|
+
rest = if rest.empty?
|
52
|
+
''
|
53
|
+
else
|
54
|
+
rest.map { |s| ' ' * 52 + s }.join("\n") << "\n"
|
55
|
+
end
|
56
|
+
|
57
|
+
' ' << cmds_str << ' ' * (48 - cmds_str.size) <<
|
58
|
+
description << "\n" << rest
|
59
|
+
end
|
124
60
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
61
|
+
# Prints usage message composed mainly
|
62
|
+
# from particular {#usage_cmd} calls.
|
63
|
+
def cmd_usage
|
64
|
+
puts "\nUSAGE: <command> [<arguments...>]\nCOMMADS:\n" <<
|
65
|
+
usage_cmd('usage', '',
|
66
|
+
'Prints this message') <<
|
67
|
+
usage_cmd('data', '',
|
68
|
+
'Lists buffered received data (hashes)') <<
|
69
|
+
usage_cmd('history', '',
|
70
|
+
'Lists all received data incl. the cleared') <<
|
71
|
+
usage_cmd('clear', '',
|
72
|
+
"Clears collected data\n" \
|
73
|
+
'(resp. moves it to history)') <<
|
74
|
+
usage_cmd('dump', '',
|
75
|
+
"Shortcut for 'data' + 'clear'") <<
|
76
|
+
usage_cmd('list', '',
|
77
|
+
'Requests for new list of devices', 'l', 'L') <<
|
78
|
+
usage_cmd('config', '',
|
79
|
+
'Requests for configuration message', 'c', 'C') <<
|
80
|
+
usage_cmd('send', '{}',
|
81
|
+
'Sends settings to connected devices',
|
82
|
+
's', 'S') <<
|
83
|
+
usage_cmd('pair', '{<timeout>}',
|
84
|
+
'Sets device into pairing mode' \
|
85
|
+
" with optional timeout\n" \
|
86
|
+
'(request for a new device)', 'n', 'N') <<
|
87
|
+
usage_cmd('ntp', '{<NTP servers...>}',
|
88
|
+
'Requests for NTP servers' \
|
89
|
+
' and optionally updates them',
|
90
|
+
'f', 'F') <<
|
91
|
+
usage_cmd('url', '{<URL> <port>}',
|
92
|
+
'Configures Cube\'s portal URL', 'u') <<
|
93
|
+
usage_cmd('wake', '{<time> <scope> [<ID>]}',
|
94
|
+
'Wake-ups the Cube',
|
95
|
+
'z', 'A') <<
|
96
|
+
usage_cmd('metadata', '{}',
|
97
|
+
'Serializes metadata for the Cube',
|
98
|
+
'm', 'M') <<
|
99
|
+
usage_cmd('delete', '{<count> <force> <RF addresses...>}',
|
100
|
+
'Deletes one or more devices from the Cube (!)',
|
101
|
+
't', 'A') <<
|
102
|
+
usage_cmd('reset', '',
|
103
|
+
'Requests for factory reset (!)', 'a', 'A') <<
|
104
|
+
usage_cmd('verbose', '',
|
105
|
+
"Toggles verbose mode (whether is incoming data\n" \
|
106
|
+
'printed immediately or is not printed)') <<
|
107
|
+
usage_cmd('save', '[a|A|all]',
|
108
|
+
"Saves buffered [all] received and sent data\n" \
|
109
|
+
"into files at '#{@save_data_dir}'") <<
|
110
|
+
usage_cmd('load', '[<path>]',
|
111
|
+
'Loads first hash from YAML file' \
|
112
|
+
" to internal variable\n" \
|
113
|
+
"-> to pass data with outgoing message\n" \
|
114
|
+
'If path is relative,' \
|
115
|
+
" it looks in '#{@load_data_dir}'\n" \
|
116
|
+
"(loads previous valid hash if no file given)\n" \
|
117
|
+
'(command can be combined' \
|
118
|
+
" using '#{ARGS_FROM_HASH}'\n" \
|
119
|
+
' with other commands' \
|
120
|
+
" which have '{}' arguments)") <<
|
121
|
+
usage_cmd('persist', '',
|
122
|
+
'Toggles persistent mode' \
|
123
|
+
"(whether is internal hash\n" \
|
124
|
+
'not invalidated after use)') <<
|
125
|
+
usage_cmd('quit', '',
|
126
|
+
"Shuts the client down gracefully\n" \
|
127
|
+
'(SIGINT and EOF also work)', 'q') <<
|
128
|
+
"\n[<arg>] means optional argument <arg>" \
|
129
|
+
"\n[<args...>] means multiple arguments <args...> or none" \
|
130
|
+
"\n (<args...> requires at least one)" \
|
131
|
+
"\n{<arg>} means that either <arg>" \
|
132
|
+
" or '#{ARGS_FROM_HASH}' is expected" \
|
133
|
+
"\n (when '#{ARGS_FROM_HASH}' specified as first argument," \
|
134
|
+
' internal hash is used' \
|
135
|
+
"\n -> 'load' command is called with rest arguments)" \
|
136
|
+
"\n ({} means that only internal hash can be used," \
|
137
|
+
"\n '#{ARGS_FROM_HASH}' is not necessary in this case)"
|
130
138
|
end
|
131
|
-
end
|
132
139
|
|
133
|
-
|
134
|
-
|
135
|
-
|
140
|
+
# Displays received hashes optionally with history.
|
141
|
+
# Calls {#buffer}.
|
142
|
+
# @param history [Boolean] whether to include history.
|
143
|
+
def list_hashes(history)
|
144
|
+
buffer(:recv, :hashes, history).each_with_index do |h, i|
|
145
|
+
puts "<#{i + 1}>"
|
146
|
+
print_hash(h)
|
147
|
+
puts
|
148
|
+
end
|
149
|
+
end
|
136
150
|
|
137
|
-
|
138
|
-
|
139
|
-
|
151
|
+
# Calls {#list_hashes} without history.
|
152
|
+
def cmd_data
|
153
|
+
list_hashes(false)
|
154
|
+
end
|
140
155
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
@buffer[:recv][sym].clear
|
156
|
+
# Calls {#list_hashes} with history.
|
157
|
+
def cmd_history
|
158
|
+
list_hashes(true)
|
145
159
|
end
|
146
|
-
end
|
147
160
|
|
148
|
-
|
149
|
-
|
150
|
-
cmd_clear
|
151
|
-
|
161
|
+
# Clears all buffered received data
|
162
|
+
# (i.e. moves them to history).
|
163
|
+
def cmd_clear
|
164
|
+
%i[data hashes].each do |sym|
|
165
|
+
@history[:recv][sym] += @buffer[:recv][sym]
|
166
|
+
@buffer[:recv][sym].clear
|
167
|
+
end
|
168
|
+
end
|
152
169
|
|
153
|
-
|
154
|
-
|
155
|
-
|
170
|
+
# Calls {#cmd_data} and {#cmd_clear}.
|
171
|
+
def cmd_dump
|
172
|
+
cmd_data
|
173
|
+
cmd_clear
|
174
|
+
end
|
156
175
|
|
157
|
-
|
158
|
-
|
159
|
-
|
176
|
+
# Calls {#send_msg} with message type 'l'
|
177
|
+
# (device list request).
|
178
|
+
def cmd_list
|
179
|
+
send_msg('l')
|
180
|
+
end
|
160
181
|
|
161
|
-
|
162
|
-
|
163
|
-
|
182
|
+
# Calls {#send_msg} with message type 'c'
|
183
|
+
# (configuration message request).
|
184
|
+
def cmd_config
|
185
|
+
send_msg('c')
|
186
|
+
end
|
164
187
|
|
165
|
-
|
166
|
-
|
167
|
-
|
188
|
+
# Calls {#send_msg} with message type 's'
|
189
|
+
# (send command message)
|
190
|
+
# and +args+ and +load_only+ option.
|
191
|
+
# @param args [Array<String>] arguments from command line.
|
192
|
+
def cmd_send(*args)
|
193
|
+
send_msg('s', *args, load_only: true)
|
194
|
+
end
|
168
195
|
|
169
|
-
|
170
|
-
|
171
|
-
|
196
|
+
# Calls {#send_msg} with message type 'n'
|
197
|
+
# (new device request)
|
198
|
+
# and +args+.
|
199
|
+
# @param args [Array<String>] arguments from command line.
|
200
|
+
def cmd_pair(*args)
|
201
|
+
send_msg('n', *args)
|
202
|
+
end
|
172
203
|
|
173
|
-
|
174
|
-
|
175
|
-
|
204
|
+
# Calls {#send_msg} with message type 'u'
|
205
|
+
# (set portal URL message)
|
206
|
+
# and +args+.
|
207
|
+
# @param args [Array<String>] arguments from command line.
|
208
|
+
def cmd_url(*args)
|
209
|
+
send_msg('u', *args)
|
210
|
+
end
|
176
211
|
|
177
|
-
|
178
|
-
|
179
|
-
|
212
|
+
# Calls {#send_msg} with message type 'f'
|
213
|
+
# (NTP servers message)
|
214
|
+
# and +args+ and +last_array+ option.
|
215
|
+
# @param args [Array<String>] arguments from command line.
|
216
|
+
def cmd_ntp(*args)
|
217
|
+
send_msg('f', *args, last_array: true)
|
218
|
+
end
|
180
219
|
|
181
|
-
|
182
|
-
|
183
|
-
|
220
|
+
# Calls {#send_msg} with message type 'z'
|
221
|
+
# (wake-up message)
|
222
|
+
# and +args+.
|
223
|
+
# @param args [Array<String>] arguments from command line.
|
224
|
+
def cmd_wake(*args)
|
225
|
+
send_msg('z', *args)
|
226
|
+
end
|
184
227
|
|
185
|
-
|
186
|
-
|
187
|
-
|
228
|
+
# Calls {#send_msg} with message type 'm'
|
229
|
+
# (metadata message)
|
230
|
+
# and +args+ and +load_only+ option.
|
231
|
+
# @param args [Array<String>] arguments from command line.
|
232
|
+
def cmd_metadata(*args)
|
233
|
+
send_msg('m', *args, load_only: true)
|
234
|
+
end
|
188
235
|
|
189
|
-
|
190
|
-
|
191
|
-
|
236
|
+
# Calls {#send_msg} with message type 't'
|
237
|
+
# (delete a device request)
|
238
|
+
# and +args+, and +last_array+ and +array_nonempty+ options.
|
239
|
+
# @param args [Array<String>] arguments from command line.
|
240
|
+
def cmd_delete(*args)
|
241
|
+
send_msg('t', *args, last_array: true, array_nonempty: true)
|
242
|
+
end
|
192
243
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
244
|
+
# Calls {#send_msg} with message type 'a'
|
245
|
+
# (factory reset request).
|
246
|
+
def cmd_reset
|
247
|
+
send_msg('a')
|
248
|
+
end
|
197
249
|
|
198
|
-
|
199
|
-
|
200
|
-
|
250
|
+
def cmd_save(what = nil)
|
251
|
+
buffer = !what
|
252
|
+
all = %w[a A all].include?(what)
|
253
|
+
unless all || buffer
|
254
|
+
puts "Unrecognized argument: '#{what}'"
|
255
|
+
return
|
256
|
+
end
|
201
257
|
|
202
|
-
|
203
|
-
|
204
|
-
all = %w[a A all].include?(what)
|
205
|
-
unless all || buffer
|
206
|
-
puts "Unrecognized argument: '#{what}'"
|
207
|
-
return
|
208
|
-
end
|
258
|
+
dir = @save_data_dir + Time.now.strftime('%Y%m%d-%H%M')
|
259
|
+
dir.mkpath
|
209
260
|
|
210
|
-
|
211
|
-
|
261
|
+
%i[recv sent].each do |sym|
|
262
|
+
data_fn = dir + (sym.to_s << '.data')
|
263
|
+
File.open(data_fn, 'w') do |f|
|
264
|
+
f.puts(buffer(sym, :data, all).join)
|
265
|
+
end
|
212
266
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
267
|
+
hashes_fn = dir + (sym.to_s << '.yaml')
|
268
|
+
File.open(hashes_fn, 'w') do |f|
|
269
|
+
buffer(sym, :hashes, all).to_yaml(f)
|
270
|
+
end
|
217
271
|
end
|
218
272
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
273
|
+
which = buffer ? 'Buffered' : 'All'
|
274
|
+
puts "#{which} received and sent raw data and hashes" \
|
275
|
+
" saved into '#{dir}'"
|
276
|
+
rescue SystemCallError => e
|
277
|
+
puts "Files could not been saved:\n#{e}"
|
223
278
|
end
|
224
279
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
puts "Files could not been saved:\n#{e}"
|
230
|
-
end
|
280
|
+
def parse_hash(path)
|
281
|
+
unless path.file? && path.readable?
|
282
|
+
return puts "File is not readable: '#{path}'"
|
283
|
+
end
|
231
284
|
|
232
|
-
|
233
|
-
|
234
|
-
|
285
|
+
hash = YAML.load_file(path)
|
286
|
+
hash = hash.first while hash.is_a?(Array)
|
287
|
+
raise YAML::SyntaxError unless hash.is_a?(Hash)
|
288
|
+
hash
|
289
|
+
rescue YAML::SyntaxError => e
|
290
|
+
puts "File '#{path}' does not contain proper YAML hash", e
|
235
291
|
end
|
236
292
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
293
|
+
def load_hash(path = nil)
|
294
|
+
if path
|
295
|
+
path = Pathname.new(path)
|
296
|
+
path = @load_data_dir + path if path.relative?
|
297
|
+
return parse_hash(path)
|
298
|
+
end
|
299
|
+
return @hash if @hash && @hash_set
|
244
300
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
301
|
+
if @hash
|
302
|
+
puts 'Internal hash is not set'
|
303
|
+
else
|
304
|
+
puts 'No internal hash loaded yet'
|
305
|
+
cmd_usage
|
306
|
+
end
|
250
307
|
end
|
251
|
-
return @hash if @hash && @hash_set
|
252
308
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
309
|
+
def assign_hash(hash)
|
310
|
+
valid_hash = !hash.nil?
|
311
|
+
@hash = hash if valid_hash
|
312
|
+
@hash_set |= valid_hash
|
313
|
+
valid_hash
|
258
314
|
end
|
259
|
-
end
|
260
315
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
316
|
+
def cmd_load(path = nil)
|
317
|
+
hash = load_hash(path)
|
318
|
+
return false unless assign_hash(hash)
|
319
|
+
print_hash(hash)
|
320
|
+
true
|
321
|
+
end
|
267
322
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
323
|
+
# Helper method that 'toggles' boolean value and prints context info.
|
324
|
+
# (It actually does not modify +flag+.)
|
325
|
+
# @param name [String] variable name.
|
326
|
+
# @param flag [Boolean] boolean value to be 'toggled'.
|
327
|
+
# @return [Boolean] negated value of +flag+.
|
328
|
+
def toggle(name, flag)
|
329
|
+
puts "#{name}: #{flag} -> #{!flag}"
|
330
|
+
!flag
|
331
|
+
end
|
274
332
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
333
|
+
# Calls {#toggle} with verbose mode variable.
|
334
|
+
def cmd_verbose
|
335
|
+
@verbose = toggle('verbose', @verbose)
|
336
|
+
end
|
279
337
|
|
280
|
-
|
281
|
-
|
338
|
+
# Calls {#toggle} with persist mode variable.
|
339
|
+
def cmd_persist
|
340
|
+
@persist = toggle('persist', @persist)
|
341
|
+
@hash_set = @persist if @hash
|
342
|
+
end
|
343
|
+
|
344
|
+
# Manages to close the client gracefully
|
345
|
+
# (it should lead to {#close}).
|
346
|
+
def cmd_quit
|
347
|
+
raise Interrupt
|
348
|
+
end
|
282
349
|
end
|
283
350
|
end
|
284
351
|
end
|