procon_bypass_man 0.1.2 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,7 +4,6 @@ require "procon_bypass_man/configuration/layer"
4
4
 
5
5
  module ProconBypassMan
6
6
  class Configuration
7
- include Validator
8
7
 
9
8
  attr_accessor :layers,
10
9
  :setting_path,
@@ -19,13 +18,12 @@ module ProconBypassMan
19
18
  @@context[@@current_context_key] ||= new
20
19
  end
21
20
 
22
- def self.switch_context(key)
23
- @@context[key] ||= new
21
+ def self.switch_new_context(key)
22
+ @@context[key] = new
24
23
  previous_key = @@current_context_key
25
24
  if block_given?
26
25
  @@current_context_key = key
27
26
  value = yield(@@context[key])
28
- @@context[key].reset!
29
27
  @@current_context_key = previous_key
30
28
  return value
31
29
  else
@@ -39,11 +37,16 @@ module ProconBypassMan
39
37
 
40
38
  MODES = [:manual]
41
39
  def layer(direction, mode: :manual, &block)
42
- unless (MODES + ProconBypassMan::Procon::ModeRegistry.plugins.keys).include?(mode)
43
- raise("#{mode} mode is unknown")
40
+ if mode.respond_to?(:name)
41
+ mode_name = mode.name.to_sym
42
+ else
43
+ mode_name = mode
44
+ end
45
+ unless (MODES + ProconBypassMan::Procon::ModeRegistry.plugins.keys).include?(mode_name)
46
+ raise("#{mode_name} mode is unknown")
44
47
  end
45
48
 
46
- layer = Layer.new(mode: mode)
49
+ layer = Layer.new(mode: mode_name)
47
50
  layer.instance_eval(&block) if block_given?
48
51
  self.layers[direction] = layer
49
52
  self
@@ -12,11 +12,11 @@ module ProconBypassMan
12
12
  end
13
13
 
14
14
  # @param [Symbol] button
15
- def flip(button, if_pressed: false, channel: nil, force_neutral: nil)
15
+ def flip(button, if_pressed: false, force_neutral: nil, flip_interval: nil)
16
16
  case if_pressed
17
17
  when TrueClass
18
18
  if_pressed = [button]
19
- when Symbol
19
+ when Symbol, String
20
20
  if_pressed = [if_pressed]
21
21
  when Array, FalseClass
22
22
  # sono mama
@@ -24,23 +24,52 @@ module ProconBypassMan
24
24
  raise "not support class"
25
25
  end
26
26
  hash = { if_pressed: if_pressed }
27
- if channel
28
- hash[:channel] = channel
29
- end
30
27
  if force_neutral
31
- hash[:force_neutral] = force_neutral
28
+ case force_neutral
29
+ when TrueClass, FalseClass
30
+ raise "ボタンを渡してください"
31
+ when Symbol, String
32
+ hash[:force_neutral] = [force_neutral]
33
+ when Array
34
+ hash[:force_neutral] = force_neutral
35
+ end
36
+ end
37
+
38
+ if flip_interval
39
+ if /\A(\d+)F\z/i =~ flip_interval
40
+ interval = ((frame = $1.to_i) / 60.0).floor(2)
41
+ else
42
+ raise "8F みたいなフォーマットで入力してください"
43
+ end
44
+ hash[:flip_interval] = interval
45
+ end
46
+ if self.flips[button]
47
+ raise "#{button}への設定をすでに割り当て済みです"
48
+ else
49
+ self.flips[button] = hash
32
50
  end
33
- self.flips[button] = hash
34
51
  end
35
52
 
36
53
  PRESET_MACROS = [:fast_return]
37
54
  def macro(name, if_pressed: )
38
- self.macros[name] = { if_pressed: if_pressed }
55
+ if name.respond_to?(:name)
56
+ macro_name = name.name.to_sym
57
+ else
58
+ macro_name = name
59
+ end
60
+ self.macros[macro_name] = { if_pressed: if_pressed }
39
61
  end
40
62
 
41
63
  def remap(button, to: )
42
- raise "シンボル以外は設定できません" unless to.is_a?(Symbol)
43
- self.remaps[button] = to
64
+ case to
65
+ when TrueClass, FalseClass
66
+ raise "ボタンを渡してください"
67
+ when Symbol, String
68
+ self.remaps[button] = { to: [to] }
69
+ when Array
70
+ raise "ボタンを渡してください" if to.size.zero?
71
+ self.remaps[button] = { to: to }
72
+ end
44
73
  end
45
74
 
46
75
  # @return [Array]
@@ -1,24 +1,22 @@
1
1
  module ProconBypassMan
2
2
  class Configuration
3
3
  module Loader
4
+ require 'digest/md5'
5
+
4
6
  def self.load(setting_path: )
5
- validation_instance = ProconBypassMan::Configuration.switch_context(:validation) do |instance|
6
- begin
7
- yaml = YAML.load_file(setting_path) or raise "読み込みに失敗しました"
8
- instance.instance_eval(yaml["setting"])
9
- rescue SyntaxError
10
- instance.errors[:base] << "Rubyのシンタックスエラーです"
11
- next(instance)
12
- rescue Psych::SyntaxError
13
- instance.errors[:base] << "yamlのシンタックスエラーです"
14
- next(instance)
7
+ ProconBypassMan::Configuration.switch_new_context(:validation) do |validation_instance|
8
+ yaml = YAML.load_file(setting_path) or raise "読み込みに失敗しました"
9
+ validation_instance.instance_eval(yaml["setting"])
10
+ validator = Validator.new(validation_instance)
11
+ if validator.valid?
12
+ next
13
+ else
14
+ raise ProconBypassMan::CouldNotLoadConfigError, validator.errors
15
15
  end
16
- instance.valid?
17
- next(instance)
18
- end
19
-
20
- if !validation_instance.errors.empty?
21
- raise ProconBypassMan::CouldNotLoadConfigError, validation_instance.errors
16
+ rescue SyntaxError
17
+ raise ProconBypassMan::CouldNotLoadConfigError, "Rubyのシンタックスエラーです"
18
+ rescue Psych::SyntaxError
19
+ raise ProconBypassMan::CouldNotLoadConfigError, "yamlのシンタックスエラーです"
22
20
  end
23
21
 
24
22
  yaml = YAML.load_file(setting_path)
@@ -33,6 +31,9 @@ module ProconBypassMan
33
31
  ProconBypassMan.logger.warn "不明なバージョンです。failoverします"
34
32
  ProconBypassMan::Configuration.instance.instance_eval(yaml["setting"])
35
33
  end
34
+
35
+ File.write(ProconBypassMan.digest_path, Digest::MD5.hexdigest(yaml["setting"]))
36
+
36
37
  ProconBypassMan::Configuration.instance
37
38
  end
38
39
 
@@ -1,13 +1,19 @@
1
1
  module ProconBypassMan
2
2
  class Configuration
3
- module Validator
3
+ class Validator
4
+ def initialize(config)
5
+ @layers = config.layers
6
+ @prefix_keys = config.prefix_keys
7
+ end
8
+
4
9
  # @return [Boolean]
5
10
  def valid?
6
11
  @errors = Hash.new {|h,k| h[k] = [] }
7
- if prefix_keys.empty?
8
- @errors[:prefix_keys] ||= []
9
- @errors[:prefix_keys] << "prefix_keys_for_changing_layerに値が入っていません。"
10
- end
12
+
13
+ validate_require_prefix_keys
14
+ validate_config_of_button_lonely
15
+ validate_verify_button_existence
16
+ validate_flip_and_remap_are_hate_each_other
11
17
 
12
18
  @errors.empty?
13
19
  end
@@ -21,6 +27,65 @@ module ProconBypassMan
21
27
  def errors
22
28
  @errors ||= Hash.new {|h,k| h[k] = [] }
23
29
  end
30
+
31
+ private
32
+
33
+ def validate_config_of_button_lonely
34
+ @layers.each do |layer_key, value|
35
+ if ProconBypassMan::Procon::ModeRegistry::PRESETS.key?(value.mode)
36
+ next
37
+ else
38
+ if !value.flips.empty? || !value.macros.empty?
39
+ @errors[:layers] << "#{layer_key}でmodeを設定しているのでボタンの設定はできません。"
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def validate_require_prefix_keys
46
+ if @prefix_keys.empty?
47
+ @errors[:prefix_keys] ||= []
48
+ @errors[:prefix_keys] << "prefix_keys_for_changing_layerに値が入っていません。"
49
+ end
50
+ end
51
+
52
+ def validate_verify_button_existence
53
+ @layers.each do |layer_key, value|
54
+ unverified_buttons = []
55
+ # teplevel target button
56
+ value.flips.keys.map(&:to_sym).each { |b| unverified_buttons << b }
57
+ value.remaps.keys.map(&:to_sym).each { |b| unverified_buttons << b }
58
+ # internal target button
59
+ value.flips.flat_map { |_flip_button, flip_option|
60
+ flip_option.flat_map { |flip_option_key, flip_option_target_button|
61
+ next if flip_option_key == :flip_interval
62
+ next if flip_option_target_button.is_a?(FalseClass) || flip_option_target_button.is_a?(TrueClass)
63
+ flip_option_target_button
64
+ }
65
+ }.compact.each { |b| unverified_buttons << b }
66
+ value.remaps.flat_map { |_button, option|
67
+ option.flat_map { |_flip_option_key, flip_option_target_button|
68
+ next if flip_option_target_button.is_a?(FalseClass) || flip_option_target_button.is_a?(TrueClass)
69
+ flip_option_target_button
70
+ }
71
+ }.compact.each { |b| unverified_buttons << b }
72
+ unless(nunsupport_buttons = (unverified_buttons - ProconBypassMan::Procon::ButtonCollection::BUTTONS)).length.zero?
73
+ @errors[:layers] << "#{layer_key}で存在しないボタン#{nunsupport_buttons.join(", ")}があります"
74
+ end
75
+ end
76
+ end
77
+
78
+ def validate_flip_and_remap_are_hate_each_other
79
+ @layers.each do |layer_key, value|
80
+ flip_buttons = []
81
+ remap_buttons = []
82
+ value.flips.keys.map(&:to_sym).each { |b| flip_buttons << b }
83
+ value.remaps.keys.map(&:to_sym).each { |b| remap_buttons << b }
84
+ if(duplicated_buttons = flip_buttons & remap_buttons).length > 0
85
+ @errors[:layers] << "レイヤー#{layer_key}で、連打とリマップの定義が重複しているボタン#{duplicated_buttons.join(", ")}があります"
86
+ end
87
+ end
88
+ end
24
89
  end
25
90
  end
26
91
  end
@@ -0,0 +1,336 @@
1
+ class ProconBypassMan::DeviceConnector
2
+ class BytesMismatchError < StandardError; end
3
+
4
+ class Value
5
+ attr_accessor :read_from, :values
6
+ def initialize(values: , read_from: )
7
+ @values = values
8
+ @read_from = read_from
9
+ end
10
+ end
11
+
12
+ PROCON_PATH = "/dev/hidraw0"
13
+ PROCON2_PATH = "/dev/hidraw1"
14
+
15
+ # 画面で再接続ができたが状況は変わらない
16
+ def self.reset_connection!
17
+ s = new
18
+ s.add([
19
+ ["0000"],
20
+ ["0000"],
21
+ ["8005"],
22
+ ["0000"],
23
+ ["8001"],
24
+ ], read_from: :switch)
25
+ s.drain_all
26
+ s.read_procon
27
+ s.write_switch("213c910080005db7723d48720a800300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
28
+ sleep(10) # いらないかも
29
+ s
30
+ end
31
+
32
+ def self.connect
33
+ s = new(throw_error_if_timeout: true, enable_at_exit: false)
34
+ s.add([
35
+ ["0000"],
36
+ ["0000"],
37
+ ["8005"],
38
+ ["0010"],
39
+ ], read_from: :switch)
40
+ # 1
41
+ s.add([["8001"]], read_from: :switch)
42
+ s.add([/^8101/], read_from: :procon) # <<< 81010003176d96e7a5480000000, macaddressとコントローラー番号を返す
43
+ # 2
44
+ s.add([["8002"]], read_from: :switch)
45
+ s.add([/^8102/], read_from: :procon)
46
+ # 3
47
+ s.add([/^0100/], read_from: :switch)
48
+ s.add([/^21/], read_from: :procon)
49
+ # 4
50
+ s.add([["8004"]], read_from: :switch)
51
+ s.drain_all
52
+ return [s.switch, s.procon]
53
+ end
54
+
55
+ def initialize(throw_error_if_timeout: false, throw_error_if_mismatch: false , enable_at_exit: true)
56
+ @stack = []
57
+ @initialized_devices = false
58
+ @throw_error_if_timeout = throw_error_if_timeout
59
+ @throw_error_if_mismatch = throw_error_if_mismatch
60
+ @enable_at_exit = enable_at_exit
61
+ end
62
+
63
+ def add(values, read_from: )
64
+ @stack << Value.new(values: values, read_from: read_from)
65
+ end
66
+
67
+ def drain_all
68
+ unless @initialized_devices
69
+ init_devices
70
+ end
71
+
72
+ while(item = @stack.shift)
73
+ item.values.each do |value|
74
+ data = nil
75
+ timer = ProconBypassMan::Timer.new
76
+ begin
77
+ timer.throw_if_timeout!
78
+ data = from_device(item).read_nonblock(128)
79
+ rescue IO::EAGAINWaitReadable
80
+ retry
81
+ end
82
+
83
+ result =
84
+ case value
85
+ when String, Array
86
+ value == data.unpack("H*")
87
+ when Regexp
88
+ value =~ data.unpack("H*").first
89
+ else
90
+ raise "#{value}は知りません"
91
+ end
92
+ if result
93
+ ProconBypassMan.logger.info "OK(expected: #{value}, got: #{data.unpack("H*")})"
94
+ else
95
+ ProconBypassMan.logger.info "NG(expected: #{value}, got: #{data.unpack("H*")})"
96
+ raise BytesMismatchError if @throw_error_if_mismatch
97
+ end
98
+ to_device(item).write_nonblock(data)
99
+ end
100
+ end
101
+ rescue ProconBypassMan::Timer::Timeout
102
+ ProconBypassMan.logger.error "timeoutになりました"
103
+ raise if @throw_error_if_timeout
104
+ end
105
+
106
+ # switchに任意の命令を入力して、switchから読み取る
107
+ def write_switch(data, only_write: false)
108
+ if data.encoding.name == "UTF-8"
109
+ data = [data].pack("H*")
110
+ end
111
+ unless @initialized_devices
112
+ init_devices
113
+ end
114
+
115
+ timer = ProconBypassMan::Timer.new
116
+ data = nil
117
+ begin
118
+ timer.throw_if_timeout!
119
+ switch.write_nonblock(data)
120
+ rescue IO::EAGAINWaitReadable
121
+ retry
122
+ rescue ProconBypassMan::Timer::Timeout
123
+ ProconBypassMan.logger.error "writeでtimeoutになりました"
124
+ raise
125
+ end
126
+ return(data.unpack("H*")) if only_write
127
+
128
+ timer = ProconBypassMan::Timer.new
129
+ begin
130
+ timer.throw_if_timeout!
131
+ data = switch.read_nonblock(128)
132
+ ProconBypassMan.logger.debug { " >>> #{data.unpack("H*")})" }
133
+ rescue IO::EAGAINWaitReadable
134
+ retry
135
+ rescue ProconBypassMan::Timer::Timeout
136
+ ProconBypassMan.logger.error "readでtimeoutになりました"
137
+ raise
138
+ end
139
+ rescue ProconBypassMan::Timer::Timeout
140
+ raise if @throw_error_if_timeout
141
+ end
142
+
143
+ def write_procon(data, only_write: false)
144
+ if data.encoding.name == "UTF-8"
145
+ data = [data].pack("H*")
146
+ end
147
+ unless @initialized_devices
148
+ init_devices
149
+ end
150
+
151
+ timer = ProconBypassMan::Timer.new
152
+ begin
153
+ timer.throw_if_timeout!
154
+ procon.write_nonblock(data)
155
+ rescue IO::EAGAINWaitReadable
156
+ retry
157
+ rescue ProconBypassMan::Timer::Timeout
158
+ ProconBypassMan.logger.error "writeでtimeoutになりました"
159
+ raise
160
+ end
161
+ return(data.unpack("H*")) if only_write
162
+
163
+ timer = ProconBypassMan::Timer.new
164
+ begin
165
+ timer.throw_if_timeout!
166
+ data = procon.read_nonblock(128)
167
+ ProconBypassMan.logger.error " <<< #{data.unpack("H*")})"
168
+ rescue IO::EAGAINWaitReadable
169
+ retry
170
+ rescue ProconBypassMan::Timer::Timeout
171
+ ProconBypassMan.logger.error "readでtimeoutになりました"
172
+ raise
173
+ end
174
+ rescue ProconBypassMan::Timer::Timeout
175
+ raise if @throw_error_if_timeout
176
+ end
177
+
178
+ def read_procon(only_read: false)
179
+ unless @initialized_devices
180
+ init_devices
181
+ end
182
+
183
+ data = nil
184
+ timer = ProconBypassMan::Timer.new
185
+ begin
186
+ timer.throw_if_timeout!
187
+ data = procon.read_nonblock(128)
188
+ ProconBypassMan.logger.debug { " <<< #{data.unpack("H*")})" }
189
+ rescue IO::EAGAINWaitReadable
190
+ retry
191
+ rescue ProconBypassMan::Timer::Timeout
192
+ ProconBypassMan.logger.error "readでtimeoutになりました"
193
+ raise
194
+ end
195
+ return(data.unpack("H*")) if only_read
196
+
197
+ timer = ProconBypassMan::Timer.new
198
+ begin
199
+ timer.throw_if_timeout!
200
+ switch.write_nonblock(data)
201
+ rescue IO::EAGAINWaitReadable
202
+ retry
203
+ rescue ProconBypassMan::Timer::Timeout
204
+ ProconBypassMan.logger.error "writeでtimeoutになりました"
205
+ raise
206
+ end
207
+ rescue ProconBypassMan::Timer::Timeout
208
+ raise if @throw_error_if_timeout
209
+ end
210
+
211
+ def read_switch(only_read: false)
212
+ unless @initialized_devices
213
+ init_devices
214
+ end
215
+
216
+ data = nil
217
+ timer = ProconBypassMan::Timer.new
218
+ begin
219
+ timer.throw_if_timeout!
220
+ data = switch.read_nonblock(128)
221
+ ProconBypassMan.logger.debug { " >>> #{data.unpack("H*")})" }
222
+ rescue IO::EAGAINWaitReadable
223
+ retry
224
+ rescue ProconBypassMan::Timer::Timeout
225
+ ProconBypassMan.logger.error "readでtimeoutになりました"
226
+ raise
227
+ end
228
+ return(data.unpack("H*")) if only_read
229
+
230
+ timer = ProconBypassMan::Timer.new
231
+ begin
232
+ timer.throw_if_timeout!
233
+ procon.write_nonblock(data)
234
+ rescue IO::EAGAINWaitReadable
235
+ retry
236
+ rescue ProconBypassMan::Timer::Timeout
237
+ ProconBypassMan.logger.error "writeでtimeoutになりました"
238
+ raise
239
+ end
240
+ rescue ProconBypassMan::Timer::Timeout
241
+ raise if @throw_error_if_timeout
242
+ end
243
+
244
+ def from_device(item)
245
+ case item.read_from
246
+ when :switch
247
+ switch
248
+ when :procon
249
+ procon
250
+ else
251
+ raise
252
+ end
253
+ end
254
+
255
+ # fromの対になる
256
+ def to_device(item)
257
+ case item.read_from
258
+ when :switch
259
+ procon
260
+ when :procon
261
+ switch
262
+ else
263
+ raise
264
+ end
265
+ end
266
+
267
+ def switch
268
+ @gadget
269
+ end
270
+
271
+ def procon
272
+ @procon
273
+ end
274
+
275
+ def is_available_device?(path)
276
+ return false if !File.exist?(path)
277
+
278
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
279
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
280
+ sleep 0.5
281
+
282
+ file = File.open(path, "w+")
283
+ begin
284
+ file.read_nonblock(128)
285
+ rescue EOFError
286
+ file.close
287
+ return false
288
+ rescue IO::EAGAINWaitReadable
289
+ file.close
290
+ return true
291
+ end
292
+ end
293
+
294
+ def to_bin(string)
295
+ string.unpack "H*"
296
+ end
297
+
298
+ def init_devices
299
+ if @initialized_devices
300
+ return
301
+ end
302
+
303
+ case
304
+ when is_available_device?(PROCON_PATH)
305
+ ProconBypassMan.logger.info "proconのデバイスファイルは#{PROCON_PATH}を使います"
306
+ @procon = File.open(PROCON_PATH, "w+")
307
+ @gadget = File.open('/dev/hidg0', "w+")
308
+ when is_available_device?(PROCON2_PATH)
309
+ ProconBypassMan.logger.info "proconのデバイスファイルは#{PROCON2_PATH}を使います"
310
+ @procon = File.open(PROCON2_PATH, "w+")
311
+ @gadget = File.open('/dev/hidg0', "w+")
312
+ else
313
+ raise "/dev/hidraw0, /dev/hidraw1の両方見つかりませんでした"
314
+ end
315
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
316
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
317
+ sleep 0.5
318
+
319
+ @initialized_devices = true
320
+
321
+ if @enable_at_exit
322
+ at_exit do
323
+ @procon&.close
324
+ @gadget&.close
325
+ end
326
+ end
327
+ rescue Errno::ENXIO => e
328
+ # /dev/hidg0 をopenできないときがある
329
+ ProconBypassMan.logger.error "Errno::ENXIO (No such device or address @ rb_sysopen - /dev/hidg0)が起きました。resetします"
330
+ ProconBypassMan.logger.error e
331
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
332
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
333
+ sleep 2
334
+ retry
335
+ end
336
+ end