sony_camera_remote_api 0.1.1

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.
@@ -0,0 +1,362 @@
1
+ module SonyCameraRemoteAPI
2
+ class CameraAPIGroupManager
3
+
4
+ # Convert exposure compensation step of ExposureCompensation API group into the real step.
5
+ def self.get_exposure_compensation_step(step)
6
+ case step
7
+ when 1 then 0.33
8
+ when 2 then 0.5
9
+ else 0
10
+ end
11
+ end
12
+
13
+ @@api_groups_all = {
14
+ ShootMode: APIGroup.new(:ShootMode,
15
+ ->(r, cond) { r[0] },
16
+ ->(r, cond) { r[1] },
17
+ ->(r, cond) { r[0] },
18
+ ->(v, avl, cond) { [v] },
19
+ end_condition: ->(v, r) { r[21]['currentShootMode'] == v },
20
+ ),
21
+ # setLiveviewSize API does not exist: we use startLiveviewWithSize API instead.
22
+ LiveviewSize: APIGroup.new(:LiveviewSize,
23
+ ->(r, cond) { r[0] },
24
+ ->(r, cond) { r[1] },
25
+ ->(r, cond) { r[0] },
26
+ nil,
27
+ ),
28
+ ZoomSetting: APIGroup.new(:ZoomSetting,
29
+ ->(r, cond) { r[0]['candidate'] },
30
+ ->(r, cond) { r[0]['candidate'] },
31
+ ->(r, cond) { r[0]['zoom'] },
32
+ ->(v, avl, cond) { [{ 'zoom' => v }] },
33
+ ),
34
+ TrackingFocus: APIGroup.new(:TrackingFocus,
35
+ ->(r, cond) { r[0]['candidate'] },
36
+ ->(r, cond) { r[0]['candidate'] },
37
+ ->(r, cond) { r[0]['trackingFocus'] },
38
+ ->(v, avl, cond) { [{ 'trackingFocus' => v }] },
39
+ ),
40
+ ContShootingMode: APIGroup.new(:ContShootingMode,
41
+ ->(r, cond) { r[0]['candidate'] },
42
+ ->(r, cond) { r[0]['candidate'] },
43
+ ->(r, cond) { r[0]['contShootingMode'] },
44
+ ->(v, avl, cond) { [{ 'contShootingMode' => v }] },
45
+ end_condition: ->(v, r) { r[38]['contShootingMode'] == v },
46
+ ),
47
+ ContShootingSpeed: APIGroup.new(:ContShootingSpeed,
48
+ ->(r, cond) { r[0]['candidate'] },
49
+ ->(r, cond) { r[0]['candidate'] },
50
+ ->(r, cond) { r[0]['contShootingSpeed'] },
51
+ ->(v, avl, cond) { [{ 'contShootingSpeed' => v }] },
52
+ end_condition: ->(v, r) { r[39]['contShootingSpeed'] == v },
53
+ ),
54
+ SelfTimer: APIGroup.new(:SelfTimer,
55
+ ->(r, cond) { r[0] },
56
+ ->(r, cond) { r[1] },
57
+ ->(r, cond) { r[0] },
58
+ ->(v, avl, cond) { [v] },
59
+ ),
60
+ ExposureMode: APIGroup.new(:ExposureMode,
61
+ ->(r, cond) { r[0] },
62
+ ->(r, cond) { r[1] },
63
+ ->(r, cond) { r[0] },
64
+ ->(v, avl, cond) { [v] },
65
+ ),
66
+ FocusMode: APIGroup.new(:FocusMode,
67
+ ->(r, cond) { r[0] },
68
+ ->(r, cond) { r[1] },
69
+ ->(r, cond) { r[0] },
70
+ ->(v, avl, cond) { [v] },
71
+ ),
72
+ # Handle parameter value by EV instead of exposure compensation index value.
73
+ ExposureCompensation: APIGroup.new(:ExposureCompensation,
74
+ # Get supported exposure compensation Array by EV.
75
+ ->(r, cond) do
76
+ ev_list = []
77
+ r.transpose.each do | max, min, step |
78
+ step = get_exposure_compensation_step step
79
+ next if step == 0
80
+ ev_list << (min..max).map { |e| (e * step).round(1) }
81
+ end
82
+ ev_list.size == 1 ? ev_list[0] : ev_list
83
+ end,
84
+ # Get available exposure compensation Array by EV.
85
+ ->(r, cond) do
86
+ max, min, step = r[1..-1]
87
+ step = get_exposure_compensation_step step
88
+ (min..max).map { |e| (e * step).round(1) }
89
+ end,
90
+ # Get current exposure compensation by EV.
91
+ ->(r, cond) do
92
+ step = cond[25]['stepIndexOfExposureCompensation']
93
+ step = get_exposure_compensation_step step
94
+ (r[0] * step).round(1)
95
+ end,
96
+ # Set exposure compensation By index from EV.
97
+ ->(v, avl, cond) do
98
+ avl.index(v) - avl.index(0)
99
+ end,
100
+ # Get exposure compensation step.
101
+ start_condition: ->(r) do
102
+ r[25]['stepIndexOfExposureCompensation'] != nil
103
+ end,
104
+ ),
105
+ FNumber: APIGroup.new(:FNumber,
106
+ ->(r, cond) { r[0] },
107
+ ->(r, cond) { r[1] },
108
+ ->(r, cond) { r[0] },
109
+ ->(v, avl, cond) { [v] },
110
+ end_condition: ->(v, r) { r[27]['currentFNumber'] == v },
111
+ ),
112
+ ShutterSpeed: APIGroup.new(:ShutterSpeed,
113
+ ->(r, cond) { r[0] },
114
+ ->(r, cond) { r[1] },
115
+ ->(r, cond) { r[0] },
116
+ ->(v, avl, cond) { [v] },
117
+ end_condition: ->(v, r) { r[32]['currentShutterSpeed'] == v },
118
+ ),
119
+ IsoSpeedRate: APIGroup.new(:IsoSpeedRate,
120
+ ->(r, cond) { r[0] },
121
+ ->(r, cond) { r[1] },
122
+ ->(r, cond) { r[0] },
123
+ ->(v, avl, cond) { [v] },
124
+ end_condition: ->(v, r) { r[29]['currentIsoSpeedRate'] == v },
125
+ ),
126
+ # Enable more intuitive parameter format as followings:
127
+ # * Hash-1 : { 'whiteBalanceMode' => mode, 'colorTemperature' => color-temperature }
128
+ # * Hash-2 : { whiteBalanceMode: mode, colorTemperature: color-temperature}
129
+ # * Array (original): [ mode, temperature-enabled-flag, color-temperature ]
130
+ WhiteBalance: APIGroup.new(:WhiteBalance,
131
+ # Get supported white-balance mode by Array of Hash.
132
+ # * delete 'colorTemperatureRange' key if unneeded.
133
+ # * get color temperature list rather than min/max/step.
134
+ ->(r, cond) do
135
+ mode_temp_list = []
136
+ r[0].map do |e|
137
+ mt = {}
138
+ mt['whiteBalanceMode'] = e['whiteBalanceMode']
139
+ if e['colorTemperatureRange'].present?
140
+ max, min, step = e['colorTemperatureRange']
141
+ mt['colorTemperature'] = (min..max).step(step).to_a
142
+ end
143
+ mode_temp_list << mt
144
+ end
145
+ mode_temp_list
146
+ end,
147
+ # Get available white-balance mode by Array of Hash, almost same as supported-accessor.
148
+ ->(r, cond) do
149
+ mode_temp_list = []
150
+ r[1].map do |e|
151
+ mt = {}
152
+ mt['whiteBalanceMode'] = e['whiteBalanceMode']
153
+ if e['colorTemperatureRange'].present?
154
+ max, min, step = e['colorTemperatureRange']
155
+ mt['colorTemperature'] = (min..max).step(step).to_a
156
+ end
157
+ mode_temp_list << mt
158
+ end
159
+ mode_temp_list
160
+ end,
161
+ # Get current white-balance mode and temperature by Hash.
162
+ # temperature key is deleted if unneeded.
163
+ ->(r, cond) do
164
+ r[0].delete_if { |k,v| k == 'colorTemperature' and v == -1 }
165
+ end,
166
+ # Set white-balance mode, converting Hash-1 to Array.
167
+ ->(v, avl, cond) do
168
+ temp_flag = v.key?('colorTemperature') ? true : false
169
+ temperature = v.key?('colorTemperature') ? v['colorTemperature'] : 0
170
+ [v['whiteBalanceMode'], temp_flag, temperature]
171
+ end,
172
+ # Accept the parameter forms as followings:
173
+ # Array and Hash-2 is converted to Hash-1.
174
+ preprocess_value: ->(v, arg, cond) do
175
+ if v.is_a? Array
176
+ ret = {}
177
+ ret['whiteBalanceMode'] = v[0]
178
+ ret['colorTemperature'] = v[2] if v[1] == true
179
+ ret
180
+ elsif v.is_a? Hash
181
+ Hash[v.map { |k, v| [k.is_a?(Symbol) ? k.to_s : k , v] }]
182
+ end
183
+ end,
184
+ # Check the given value is available by
185
+ # * comparing mode
186
+ # * color temperature value is included in 'colorTemperature' array
187
+ # when Color Temperature mode
188
+ check_availability: ->(v, avl, cond) do
189
+ # check mode
190
+ sel = avl.find {|e| v['whiteBalanceMode'] == e['whiteBalanceMode'] }
191
+ return false if sel.nil?
192
+
193
+ if sel.key? 'colorTemperatureRange'
194
+ # temperature
195
+ return true if sel['colorTemperature'].include? v['colorTemperature']
196
+ false
197
+ else
198
+ true
199
+ end
200
+ end,
201
+ ),
202
+ # ProgramShift: APIGroup.new(:ProgramShift,
203
+ # ->(v, cond){ v[0] },
204
+ # ->(v, cond){ v[1] },
205
+ # ->(v, cond){ v[0] },
206
+ # ->(v, avl, cond){ [v] },
207
+ # ),
208
+ FlashMode: APIGroup.new(:FlashMode,
209
+ ->(r, cond) { r[0] },
210
+ ->(r, cond) { r[1] },
211
+ ->(r, cond) { r[0] },
212
+ ->(v, avl, cond) { [v] }
213
+ ),
214
+ # Enable more intuitive parameter format as followings:
215
+ # * Hash-1 : { 'aspect' => aspect, 'size' => size }
216
+ # * Hash-2 : { aspect: aspect, size: size }
217
+ # * Array (original) : [ aspect, size ]
218
+ # make setStillSize accept Hash as parameter, because getSupported/AvailableStillSize returns Hash
219
+ StillSize: APIGroup.new(:StillSize,
220
+ # Get supported still size and aspect by Array of Hash.
221
+ ->(r, cond) { r[0] },
222
+ # Get available still size and aspect by Array of Hash.
223
+ ->(r, cond) { r[1] },
224
+ # Get current still size and aspect by Hash.
225
+ ->(r, cond) { r[0] },
226
+ # Set still size and aspect, converting Hash-1 to Array.
227
+ ->(v, avl, cond) { [v['aspect'], v['size']] },
228
+ # Accept the parameter forms as followings:
229
+ # Array and Hash-2 is converted to Hash-1.
230
+ preprocess_value: ->(v, arg, cond) do
231
+ if v.is_a? Array
232
+ ret = {}
233
+ ret['aspect'] = v[0]
234
+ ret['size'] = v[1]
235
+ ret
236
+ elsif v.is_a? Hash
237
+ Hash[v.map { |k, v| [k.is_a?(Symbol) ? k.to_s : k , v] }]
238
+ end
239
+ end,
240
+ ),
241
+ StillQuality: APIGroup.new(:StillQuality,
242
+ ->(r, cond) { r[0]['candidate'] },
243
+ ->(r, cond) { r[0]['candidate'] },
244
+ ->(r, cond) { r[0]['stillQuality'] },
245
+ ->(v, avl, cond) { [{ 'stillQuality' => v }] },
246
+ ),
247
+ PostviewImageSize: APIGroup.new(:PostviewImageSize,
248
+ ->(r, cond) { r[0] },
249
+ ->(r, cond) { r[1] },
250
+ ->(r, cond) { r[0] },
251
+ ->(v, avl, cond) { [v] },
252
+ ),
253
+ MovieFileFormat: APIGroup.new(:MovieFileFormat,
254
+ ->(r, cond) { r[0]['candidate'] },
255
+ ->(r, cond) { r[0]['candidate'] },
256
+ ->(r, cond) { r[0]['movieFileFormat'] },
257
+ ->(v, avl, cond) { [{ 'movieFileFormat' => v }] },
258
+ end_condition: ->(v, r) { r[45]['movieFileFormat'] == v },
259
+ ),
260
+ MovieQuality: APIGroup.new(:MovieQuality,
261
+ ->(r, cond) { r[0] },
262
+ ->(r, cond) { r[1] },
263
+ ->(r, cond) { r[0] },
264
+ ->(v, avl, cond) { [v] },
265
+ end_condition: ->(v, r) { r[13]['currentMovieQuality'] == v },
266
+ ),
267
+ SteadyMode: APIGroup.new(:SteadyMode,
268
+ ->(r, cond) { r[0] },
269
+ ->(r, cond) { r[1] },
270
+ ->(r, cond) { r[0] },
271
+ ->(v, avl, cond) { [v] },
272
+ ),
273
+ ViewAngle: APIGroup.new(:ViewAngle,
274
+ ->(r, cond) { r[0] },
275
+ ->(r, cond) { r[1] },
276
+ ->(r, cond) { r[0] },
277
+ ->(v, avl, cond) { [v] },
278
+ ),
279
+ SceneSelection: APIGroup.new(:SceneSelection,
280
+ ->(r, cond) { r[0]['candidate'] },
281
+ ->(r, cond) { r[0]['candidate'] },
282
+ ->(r, cond) { r[0]['scene'] },
283
+ ->(v, avl, cond) { [{ 'scene' => v }] },
284
+ ),
285
+ ColorSetting: APIGroup.new(:ColorSetting,
286
+ ->(r, cond) { r[0]['candidate'] },
287
+ ->(r, cond) { r[0]['candidate'] },
288
+ ->(r, cond) { r[0]['colorSetting'] },
289
+ ->(v, avl, cond) { [{ 'colorSetting' => v }] },
290
+ ),
291
+ IntervalTime: APIGroup.new(:IntervalTime,
292
+ ->(r, cond) { r[0]['candidate'] },
293
+ ->(r, cond) { r[0]['candidate'] },
294
+ ->(r, cond) { r[0]['intervalTimeSec'] },
295
+ ->(v, avl, cond) { [{ 'intervalTimeSec' => v }] },
296
+ ),
297
+ LoopRecTime: APIGroup.new(:LoopRecTime,
298
+ ->(r, cond) { r[0]['candidate'] },
299
+ ->(r, cond) { r[0]['candidate'] },
300
+ ->(r, cond) { r[0]['loopRecTimeMin'] },
301
+ ->(v, avl, cond) { [{ 'loopRecTimeMin' => v }] },
302
+ ),
303
+ WindNoiseReduction: APIGroup.new(:WindNoiseReduction,
304
+ ->(r, cond) { r[0]['candidate'] },
305
+ ->(r, cond) { r[0]['candidate'] },
306
+ ->(r, cond) { r[0]['windNoiseReduction'] },
307
+ ->(v, avl, cond) { [{ 'windNoiseReduction' => v }] },
308
+ ),
309
+ AudioRecording: APIGroup.new(:AudioRecording,
310
+ ->(r, cond) { r[0]['candidate'] },
311
+ ->(r, cond) { r[0]['candidate'] },
312
+ ->(r, cond) { r[0]['audioRecording'] },
313
+ ->(v, avl, cond) { [{ 'audioRecording' => v }] },
314
+ ),
315
+ FlipSetting: APIGroup.new(:FlipSetting,
316
+ ->(r, cond) { r[0]['candidate'] },
317
+ ->(r, cond) { r[0]['candidate'] },
318
+ ->(r, cond) { r[0]['flip'] },
319
+ ->(v, avl, cond) { [{ 'flip' => v }] },
320
+ ),
321
+ TvColorSystem: APIGroup.new(:TvColorSystem,
322
+ ->(r, cond) { r[0]['candidate'] },
323
+ ->(r, cond) { r[0]['candidate'] },
324
+ ->(r, cond) { r[0]['tvColorSystem'] },
325
+ ->(v, avl, cond) { [{ 'tvColorSystem' => v }] },
326
+ ),
327
+ # 'cameraFunctionResult' does not work depending on the timing of setCameraFunction call...
328
+ CameraFunction: APIGroup.new(:CameraFunction,
329
+ ->(r, cond) { r[0] },
330
+ ->(r, cond) { r[1] },
331
+ ->(r, cond) { r[0] },
332
+ ->(v, avl, cond) { [v] },
333
+ end_condition: ->(v, r) do
334
+ # r[15]['cameraFunctionResult'] == 'Success'
335
+ r[12]['currentCameraFunction'] == v
336
+ end
337
+ ),
338
+ InfraredRemoteControl: APIGroup.new(:InfraredRemoteControl,
339
+ ->(r, cond) { r[0]['candidate'] },
340
+ ->(r, cond) { r[0]['candidate'] },
341
+ ->(r, cond) { r[0]['infraredRemoteControl'] },
342
+ ->(v, avl, cond) { [{ 'infraredRemoteControl' => v }] },
343
+ ),
344
+ AutoPowerOff: APIGroup.new(:AutoPowerOff,
345
+ ->(r, cond) { r[0]['candidate'] },
346
+ ->(r, cond) { r[0]['candidate'] },
347
+ ->(r, cond) { r[0]['autoPowerOff'] },
348
+ ->(v, avl, cond) { [{ 'autoPowerOff' => v }] },
349
+ ),
350
+ BeepMode: APIGroup.new(:BeepMode,
351
+ ->(r, cond) { r[0] },
352
+ ->(r, cond) { r[1] },
353
+ ->(r, cond) { r[0] },
354
+ ->(v, avl, cond) { [v] },
355
+ ),
356
+ }
357
+
358
+ end
359
+ end
360
+
361
+
362
+
@@ -0,0 +1,266 @@
1
+ require 'sony_camera_remote_api'
2
+ require 'sony_camera_remote_api/scripts'
3
+ require 'sony_camera_remote_api/logging'
4
+ require 'sony_camera_remote_api/utils'
5
+ require 'fileutils'
6
+ require 'thor'
7
+ require 'highline/import'
8
+ require 'yaml'
9
+ require 'pp'
10
+
11
+ module SonyCameraRemoteAPI
12
+ module Client
13
+ module ConfigUtils
14
+
15
+ module_function
16
+
17
+ # Get default selected camera.
18
+ def default_camera(file)
19
+ # check default camera in configuration file
20
+ yaml = read_config_file file
21
+ unless yaml.key?('default')
22
+ puts 'Default camera is not selected!'
23
+ return nil
24
+ end
25
+ yaml['camera'].find { |n| n['ssid'] == yaml['default'] }
26
+ end
27
+
28
+ # Save endpoint information to config file if exists
29
+ def save_ssdp_config(file, endpoints)
30
+ yaml = read_config_file file
31
+ config = yaml['camera'].find { |n| n['ssid'] == yaml['default'] }
32
+ config['endpoints'] = endpoints
33
+ write_config_file file, yaml
34
+ end
35
+
36
+ # Read config file.
37
+ # @param [Boolean] assert If +true+, exit when the config file does not exist.
38
+ # @return [Hash] JSON converted from YAML config file.
39
+ def read_config_file(file, assert: true)
40
+ if File.exists? file
41
+ YAML.load_file(file)
42
+ else
43
+ if assert
44
+ puts 'Configuration file not found!'
45
+ exit 1
46
+ end
47
+ end
48
+ end
49
+
50
+ # Write config file
51
+ def write_config_file(file, yaml)
52
+ open(file, 'w') do |e|
53
+ YAML.dump(yaml, e)
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
60
+
61
+
62
+
63
+ module SonyCameraRemoteAPI
64
+ # CLI client module
65
+ module Client
66
+
67
+ # 'config' subcommand class for managing camera connection
68
+ class Config < Thor
69
+ include Utils
70
+ include Scripts
71
+ include ConfigUtils
72
+
73
+ class_option :file, aliases: '-f', type: :string, desc: 'Config file path', banner: 'FILE'
74
+
75
+ no_tasks do
76
+ def config_file
77
+ options[:file] || GLOBAL_CONFIG_FILE
78
+ end
79
+ end
80
+
81
+
82
+ desc 'add <SSID> <password> <interface>', 'Register a new camera connection'
83
+ def add(ssid, pass, interface)
84
+ yaml = read_config_file config_file, assert: false
85
+ if yaml.nil? || !yaml.key?('camera')
86
+ yaml = { 'camera' => [{ 'ssid' => ssid, 'pass' => pass, 'interface' => interface }] }
87
+ else
88
+ # if input SSID is already registered, ask user to overwrite
89
+ index = yaml['camera'].index { |n| n['ssid'] == ssid }
90
+ if index
91
+ answer = $terminal.ask('SSID duplicated! Do you want to overwrite? ') { |q| q.validate = /[yn]/i; q.default = 'n' }
92
+ if answer == 'y'
93
+ yaml['camera'][index] = { 'ssid' => ssid, 'pass' => pass, 'interface' => interface }
94
+ else
95
+ puts 'Entry not changed.'
96
+ invoke :list, [], options
97
+ return
98
+ end
99
+ else
100
+ yaml['camera'] << { 'ssid' => ssid, 'pass' => pass, 'interface' => interface }
101
+ end
102
+ end
103
+ # ask user to select as default
104
+ answer = $terminal.ask('Do you want set this camera as default? ') { |q| q.validate = /[yn]/i; q.default = 'y' }
105
+ if answer == 'y'
106
+ yaml['default'] = ssid
107
+ end
108
+ write_config_file config_file, yaml
109
+ invoke :list, [], options
110
+ end
111
+
112
+
113
+ desc 'list', 'List registered cameras'
114
+ def list
115
+ yaml = read_config_file config_file
116
+ if yaml['camera'].uniq! { |v| v['ssid'] }
117
+ puts 'Removed duplicated entries.'
118
+ write_config_file config_file, yaml
119
+ end
120
+ if yaml.key? 'camera'
121
+ # selected camera is signed by allow
122
+ yaml['camera'].each_with_index do |v, i|
123
+ if v['ssid'] == yaml['default']
124
+ puts "=> #{i}: SSID : #{v['ssid']} "
125
+ else
126
+ puts " #{i}: SSID : #{v['ssid']} "
127
+ end
128
+ puts " Password : #{v['pass']} "
129
+ puts " Interface : #{v['interface']} "
130
+ end
131
+ else
132
+ # no camera is registered
133
+ puts 'No camera!'
134
+ puts "To add new camera connection, use 'sonycam config add' command."
135
+ end
136
+ # default camera is not selected
137
+ unless yaml.key? 'default'
138
+ puts 'Currently no camera is selected as default!'
139
+ puts "To select a camera as default from the list above, use 'sonycam config use' command."
140
+ end
141
+ end
142
+
143
+
144
+ desc 'remove [options]', 'Unregister a camera'
145
+ option :all, type: :boolean, desc: 'Remove all cameras'
146
+ option :id, aliases: '-i', type: :numeric, desc: "Specify camera by ID, which can be seen by 'config list' command", banner: 'NUMBER'
147
+ option :ssid, aliases: '-s', type: :string, desc: 'Specify camera by SSID'
148
+ def remove
149
+ unless [options[:id], options[:ssid], options[:all]].one?
150
+ puts "use either option '--all', '--id', '--ssid' to specify camera"
151
+ return
152
+ end
153
+
154
+ yaml = read_config_file config_file
155
+ if options[:all]
156
+ # remove all entries
157
+ write_config_file config_file, {}
158
+ return
159
+ end
160
+ if options[:id]
161
+ # remove ID'th entry
162
+ if 0 <= options[:id] && options[:id] < yaml['camera'].size
163
+ yaml.delete_if { |k, v| k == 'default' && v == yaml['camera'][options[:id]]['ssid'] }
164
+ yaml['camera'].delete_at options[:id]
165
+ write_config_file config_file, yaml
166
+ else
167
+ puts 'ERROR: Specified ID is invalid!'
168
+ end
169
+ elsif options[:ssid]
170
+ # find entry that matches specified SSID exactly
171
+ result, num = partial_and_unique_match(options[:ssid], yaml['camera'].map { |e| e['ssid'] })
172
+ if result
173
+ yaml.delete_if { |k, v| k == 'default' && v == result }
174
+ yaml['camera'].delete_if { |e| e['ssid'] == result }
175
+ write_config_file config_file, yaml
176
+ else
177
+ if num > 1
178
+ puts 'ERROR: Specified SSID is ambigous!'
179
+ elsif num == 0
180
+ puts 'ERROR: Specified SSID is not found!'
181
+ end
182
+ end
183
+ end
184
+ invoke :list, [], options
185
+ end
186
+
187
+
188
+ desc 'use <SSID>', 'Select a camera as default'
189
+ option :id, aliases: '-i', type: :numeric, desc: "Specify camera by ID, which can be seen by 'config list' command", banner: 'NUMBER'
190
+ option :ssid, aliases: '-s', type: :string, desc: 'Specify camera by SSID'
191
+ def use
192
+ unless [options[:id], options[:ssid]].one?
193
+ puts "use either option '--id' or '--ssid' to specify camera"
194
+ return
195
+ end
196
+
197
+ yaml = read_config_file config_file
198
+
199
+ if options[:id]
200
+ # select ID'th entry
201
+ if 0 <= options[:id] && options[:id] < yaml['camera'].size
202
+ yaml['default'] = yaml['camera'][options[:id]]['ssid']
203
+ write_config_file config_file, yaml
204
+ else
205
+ puts 'ERROR: Specified ID is invalid!'
206
+ end
207
+ elsif options[:ssid]
208
+ # find entry that matches specified SSID exactly
209
+ result, num = partial_and_unique_match(options[:ssid], yaml['camera'].map { |e| e['ssid'] })
210
+ if result
211
+ yaml['default'] = result
212
+ write_config_file config_file, yaml
213
+ else
214
+ # find entry that matches specified SSID partially but identically
215
+ if num > 1
216
+ puts 'ERROR: Specified SSID is ambigous!'
217
+ elsif num == 0
218
+ puts 'ERROR: Specified SSID is not found!'
219
+ end
220
+ end
221
+ end
222
+ invoke :list, [], options
223
+ end
224
+
225
+
226
+ desc 'default', 'Show the current default camera'
227
+ option :json, type: :boolean, desc: 'output in JSON format'
228
+ def default
229
+ config = default_camera config_file
230
+ return if config.nil?
231
+
232
+ if options[:json]
233
+ puts JSON.pretty_generate config
234
+ else
235
+ puts "SSID : #{config['ssid']} "
236
+ puts "Password : #{config['pass']} "
237
+ puts "Interface : #{config['interface']} "
238
+ end
239
+ end
240
+
241
+
242
+ desc 'connect', 'Connect to the current default camera'
243
+ option :restart, aliases: '-r', type: :boolean, desc: 'Restart interface', default: false
244
+ def connect
245
+ config = default_camera config_file
246
+ return if config.nil?
247
+
248
+ puts 'Selected camera:'
249
+ puts " - SSID : #{config['ssid']}"
250
+ puts " - pass : #{config['pass']}"
251
+ puts " - inteface : #{config['interface']}"
252
+
253
+ # Connect to camera by external script
254
+ if options[:restart]
255
+ result = Scripts.restart_and_connect(config['interface'], config['ssid'], config['pass'])
256
+ else
257
+ result = Scripts.connect(config['interface'], config['ssid'], config['pass'])
258
+ end
259
+ unless result
260
+ puts 'Failed to connect!'
261
+ exit 1
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end