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.
- checksums.yaml +7 -0
- data/.gitignore +55 -0
- data/.rspec +2 -0
- data/.simplecov +16 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +78 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/Rakefile +145 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/sonycam +6 -0
- data/lib/core_ext/hash_patch.rb +15 -0
- data/lib/sony_camera_remote_api/camera_api.rb +199 -0
- data/lib/sony_camera_remote_api/camera_api_group.rb +281 -0
- data/lib/sony_camera_remote_api/camera_api_group_def.rb +362 -0
- data/lib/sony_camera_remote_api/client/config.rb +266 -0
- data/lib/sony_camera_remote_api/client/main.rb +646 -0
- data/lib/sony_camera_remote_api/error.rb +41 -0
- data/lib/sony_camera_remote_api/logging.rb +76 -0
- data/lib/sony_camera_remote_api/packet.rb +109 -0
- data/lib/sony_camera_remote_api/raw_api.rb +196 -0
- data/lib/sony_camera_remote_api/scripts.rb +64 -0
- data/lib/sony_camera_remote_api/ssdp.rb +72 -0
- data/lib/sony_camera_remote_api/utils.rb +98 -0
- data/lib/sony_camera_remote_api/version.rb +3 -0
- data/lib/sony_camera_remote_api.rb +1044 -0
- data/scripts/Linux/add_ssdp_route.sh +39 -0
- data/scripts/Linux/connect_wifi.sh +114 -0
- data/scripts/connect.sh +36 -0
- data/sony_camera_remote_api.gemspec +35 -0
- metadata +231 -0
@@ -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
|