procon_bypass_man 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +18 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +10 -0
  8. data/Gemfile.lock +42 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +157 -0
  11. data/Rakefile +4 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/docs/how_to_connect_procon.md +23 -0
  15. data/docs/setup_raspi.md +38 -0
  16. data/examples/practical/app.rb +16 -0
  17. data/examples/practical/setting.yml +24 -0
  18. data/examples/simple.rb +13 -0
  19. data/lib/procon_bypass_man.rb +66 -0
  20. data/lib/procon_bypass_man/bypass.rb +53 -0
  21. data/lib/procon_bypass_man/configuration.rb +83 -0
  22. data/lib/procon_bypass_man/configuration/layer.rb +52 -0
  23. data/lib/procon_bypass_man/configuration/loader.rb +44 -0
  24. data/lib/procon_bypass_man/configuration/validator.rb +26 -0
  25. data/lib/procon_bypass_man/device_registry.rb +41 -0
  26. data/lib/procon_bypass_man/io_monitor.rb +89 -0
  27. data/lib/procon_bypass_man/processor.rb +17 -0
  28. data/lib/procon_bypass_man/procon.rb +129 -0
  29. data/lib/procon_bypass_man/procon/button_collection.rb +40 -0
  30. data/lib/procon_bypass_man/procon/data.rb +5 -0
  31. data/lib/procon_bypass_man/procon/layer_changeable.rb +28 -0
  32. data/lib/procon_bypass_man/procon/macro_registry.rb +48 -0
  33. data/lib/procon_bypass_man/procon/mode_registry.rb +46 -0
  34. data/lib/procon_bypass_man/procon/pressed_button_helper.rb +25 -0
  35. data/lib/procon_bypass_man/procon/user_operation.rb +72 -0
  36. data/lib/procon_bypass_man/runner.rb +171 -0
  37. data/lib/procon_bypass_man/version.rb +5 -0
  38. data/procon_bypass_man.gemspec +35 -0
  39. metadata +82 -0
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ gem 'procon_bypass_man', github: 'splaspla-hacker/procon_bypass_man', branch: "edge"
8
+ gem 'procon_bypass_man-splatoon2', github: 'splaspla-hacker/procon_bypass_man-splatoon2', branch: "0.1.0"
9
+ end
10
+
11
+ ProconBypassMan.tap do |pbm|
12
+ pbm.logger = "./app.log"
13
+ pbm.logger.level = :debug
14
+ end
15
+
16
+ ProconBypassMan.run(setting_path: "./setting.yml")
@@ -0,0 +1,24 @@
1
+ version: 1.0
2
+ setting: |-
3
+ fast_return = ProconBypassMan::Splatoon2::Macro::FastReturn
4
+ guruguru = ProconBypassMan::Splatoon2::Mode::Guruguru
5
+
6
+ install_macro_plugin fast_return
7
+ install_mode_plugin guruguru
8
+
9
+ prefix_keys_for_changing_layer [:zr, :r, :zl, :l]
10
+
11
+ layer :up, mode: :manual do
12
+ flip :zr, if_pressed: :zr, force_neutral: :zl
13
+ flip :zl, if_pressed: [:y, :b, :zl]
14
+ flip :down, if_pressed: :down
15
+ macro fast_return.name, if_pressed: [:y, :b, :down]
16
+ remap :l, to: :zr
17
+ end
18
+ layer :right, mode: guruguru.name
19
+ layer :left do
20
+ # no-op
21
+ end
22
+ layer :down do
23
+ flip :zl
24
+ end
@@ -0,0 +1,13 @@
1
+ ProconBypassMan.run do
2
+ prefix_keys_for_changing_layer [:zr, :r, :zl, :l]
3
+
4
+ layer :up, mode: :manual do
5
+ flip :zr, if_pressed: :zr
6
+ end
7
+ layer :right
8
+ layer :left
9
+ layer :down do
10
+ flip :zl
11
+ end
12
+ end
13
+
@@ -0,0 +1,66 @@
1
+ require "logger"
2
+ require 'yaml'
3
+
4
+ require_relative "procon_bypass_man/version"
5
+ require_relative "procon_bypass_man/device_registry"
6
+ require_relative "procon_bypass_man/bypass"
7
+ require_relative "procon_bypass_man/runner"
8
+ require_relative "procon_bypass_man/processor"
9
+ require_relative "procon_bypass_man/procon/data"
10
+ require_relative "procon_bypass_man/configuration"
11
+ require_relative "procon_bypass_man/procon"
12
+
13
+ STDOUT.sync = true
14
+ Thread.abort_on_exception = true
15
+
16
+ module ProconBypassMan
17
+ class ProConRejected < StandardError; end
18
+ class CouldNotLoadConfigError < StandardError; end
19
+ class CouldNotConnectDeviceError < StandardError; end
20
+
21
+ def self.configure(setting_path: nil, &block)
22
+ unless setting_path
23
+ logger.warn "setting_pathが未設定です。設定ファイルのライブリロードが使えません。"
24
+ end
25
+
26
+ if block_given?
27
+ ProconBypassMan::Configuration.instance.instance_eval(&block)
28
+ else
29
+ ProconBypassMan::Configuration::Loader.load(setting_path: setting_path)
30
+ end
31
+ end
32
+
33
+ def self.run(setting_path: nil, &block)
34
+ configure(setting_path: setting_path, &block)
35
+ registry = ProconBypassMan::DeviceRegistry.new
36
+ Runner.new(gadget: registry.gadget, procon: registry.procon).run
37
+ rescue CouldNotLoadConfigError
38
+ ProconBypassMan.logger.error "設定ファイルが不正です。設定ファイルの読み込みに失敗しました"
39
+ puts "設定ファイルが不正です。設定ファイルの読み込みに失敗しました"
40
+ exit 1
41
+ rescue CouldNotConnectDeviceError
42
+ ProconBypassMan.logger.error "デバイスと接続中です"
43
+ puts "デバイスと接続中です"
44
+ retry
45
+ end
46
+
47
+ def self.logger=(dev)
48
+ @@logger = Logger.new(dev, 5, 1024 * 1024 * 10) # 5世代まで残して, 10MBでローテーション
49
+ end
50
+
51
+ def self.logger
52
+ if defined?(@@logger)
53
+ @@logger
54
+ else
55
+ Logger.new(nil)
56
+ end
57
+ end
58
+
59
+ def self.reset!
60
+ ProconBypassMan::Procon::MacroRegistry.reset!
61
+ ProconBypassMan::Procon::ModeRegistry.reset!
62
+ ProconBypassMan::Procon.reset!
63
+ ProconBypassMan::Configuration.instance.reset!
64
+ ProconBypassMan::IOMonitor.reset!
65
+ end
66
+ end
@@ -0,0 +1,53 @@
1
+ class ProconBypassMan::Bypass
2
+ attr_accessor :gadget, :procon, :monitor
3
+ def initialize(gadget: , procon: , monitor: )
4
+ self.gadget = gadget
5
+ self.procon = procon
6
+ self.monitor = monitor
7
+ end
8
+
9
+ # ゆっくりでいい
10
+ def send_gadget_to_procon!
11
+ monitor.record(:start_function)
12
+ input = nil
13
+ begin
14
+ sleep($will_interval_1_6)
15
+ input = self.gadget.read_nonblock(128)
16
+ ProconBypassMan.logger.debug { ">>> #{input.unpack("H*")}" }
17
+ rescue IO::EAGAINWaitReadable
18
+ monitor.record(:eagain_wait_readable_on_read)
19
+ return if $will_terminate_token
20
+ retry
21
+ end
22
+
23
+ begin
24
+ self.procon.write_nonblock(input)
25
+ rescue IO::EAGAINWaitReadable
26
+ monitor.record(:eagain_wait_readable_on_write)
27
+ return
28
+ end
29
+ monitor.record(:end_function)
30
+ end
31
+
32
+ def send_procon_to_gadget!
33
+ monitor.record(:start_function)
34
+ output = nil
35
+ begin
36
+ sleep($will_interval_0_0_0_5)
37
+ output = self.procon.read_nonblock(128)
38
+ ProconBypassMan.logger.debug { "<<< #{output.unpack("H*")}" }
39
+ rescue IO::EAGAINWaitReadable
40
+ monitor.record(:eagain_wait_readable_on_read)
41
+ return if $will_terminate_token
42
+ retry
43
+ end
44
+
45
+ begin
46
+ self.gadget.write_nonblock(ProconBypassMan::Processor.new(output).process)
47
+ rescue IO::EAGAINWaitReadable
48
+ monitor.record(:eagain_wait_readable_on_write)
49
+ return
50
+ end
51
+ monitor.record(:end_function)
52
+ end
53
+ end
@@ -0,0 +1,83 @@
1
+ require "procon_bypass_man/configuration/validator"
2
+ require "procon_bypass_man/configuration/loader"
3
+ require "procon_bypass_man/configuration/layer"
4
+
5
+ module ProconBypassMan
6
+ class Configuration
7
+ include Validator
8
+
9
+ attr_accessor :layers,
10
+ :setting_path,
11
+ :mode_plugins,
12
+ :macro_plugins,
13
+ :context,
14
+ :current_context_key
15
+
16
+ def self.instance
17
+ @@current_context_key ||= :main
18
+ @@context ||= {}
19
+ @@context[@@current_context_key] ||= new
20
+ end
21
+
22
+ def self.switch_context(key)
23
+ @@context[key] ||= new
24
+ previous_key = @@current_context_key
25
+ if block_given?
26
+ @@current_context_key = key
27
+ value = yield(@@context[key])
28
+ @@context[key].reset!
29
+ @@current_context_key = previous_key
30
+ return value
31
+ else
32
+ @@current_context_key = key
33
+ end
34
+ end
35
+
36
+ def initialize
37
+ reset!
38
+ end
39
+
40
+ MODES = [:manual]
41
+ def layer(direction, mode: :manual, &block)
42
+ unless (MODES + ProconBypassMan::Procon::ModeRegistry.plugins.keys).include?(mode)
43
+ raise("#{mode} mode is unknown")
44
+ end
45
+
46
+ layer = Layer.new(mode: mode)
47
+ layer.instance_eval(&block) if block_given?
48
+ self.layers[direction] = layer
49
+ self
50
+ end
51
+
52
+ def install_mode_plugin(klass)
53
+ ProconBypassMan::Procon::ModeRegistry.install_plugin(klass)
54
+ self
55
+ end
56
+
57
+ def install_macro_plugin(klass)
58
+ ProconBypassMan::Procon::MacroRegistry.install_plugin(klass)
59
+ self
60
+ end
61
+
62
+ def prefix_keys_for_changing_layer(buttons)
63
+ @prefix_keys_for_changing_layer = buttons
64
+ self
65
+ end
66
+
67
+ def prefix_keys
68
+ @prefix_keys_for_changing_layer
69
+ end
70
+
71
+ def reset!
72
+ @prefix_keys_for_changing_layer = []
73
+ self.mode_plugins = {}
74
+ self.macro_plugins = {}
75
+ self.layers = {
76
+ up: Layer.new,
77
+ down: Layer.new,
78
+ left: Layer.new,
79
+ right: Layer.new,
80
+ }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,52 @@
1
+ module ProconBypassMan
2
+ class Configuration
3
+ class Layer
4
+ attr_accessor :mode, :flips, :macros, :remaps
5
+
6
+ def initialize(mode: :manual, &block)
7
+ self.mode = mode
8
+ self.flips = {}
9
+ self.macros = {}
10
+ self.remaps = {}
11
+ instance_eval(&block) if block_given?
12
+ end
13
+
14
+ # @param [Symbol] button
15
+ def flip(button, if_pressed: false, channel: nil, force_neutral: nil)
16
+ case if_pressed
17
+ when TrueClass
18
+ if_pressed = [button]
19
+ when Symbol
20
+ if_pressed = [if_pressed]
21
+ when Array, FalseClass
22
+ # sono mama
23
+ else
24
+ raise "not support class"
25
+ end
26
+ hash = { if_pressed: if_pressed }
27
+ if channel
28
+ hash[:channel] = channel
29
+ end
30
+ if force_neutral
31
+ hash[:force_neutral] = force_neutral
32
+ end
33
+ self.flips[button] = hash
34
+ end
35
+
36
+ PRESET_MACROS = [:fast_return]
37
+ def macro(name, if_pressed: )
38
+ self.macros[name] = { if_pressed: if_pressed }
39
+ end
40
+
41
+ def remap(button, to: )
42
+ raise "シンボル以外は設定できません" unless to.is_a?(Symbol)
43
+ self.remaps[button] = to
44
+ end
45
+
46
+ # @return [Array]
47
+ def flip_buttons
48
+ flips
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ module ProconBypassMan
2
+ class Configuration
3
+ module Loader
4
+ 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)
15
+ end
16
+ instance.valid?
17
+ next(instance)
18
+ end
19
+
20
+ if !validation_instance.errors.empty?
21
+ raise ProconBypassMan::CouldNotLoadConfigError, validation_instance.errors
22
+ end
23
+
24
+ yaml = YAML.load_file(setting_path)
25
+ ProconBypassMan::Configuration.instance.setting_path = setting_path
26
+ ProconBypassMan::Configuration.instance.reset!
27
+ ProconBypassMan.reset!
28
+
29
+ case yaml["version"]
30
+ when 1.0, nil
31
+ ProconBypassMan::Configuration.instance.instance_eval(yaml["setting"])
32
+ else
33
+ ProconBypassMan.logger.warn "不明なバージョンです。failoverします"
34
+ ProconBypassMan::Configuration.instance.instance_eval(yaml["setting"])
35
+ end
36
+ ProconBypassMan::Configuration.instance
37
+ end
38
+
39
+ def self.reload_setting
40
+ self.load(setting_path: ProconBypassMan::Configuration.instance.setting_path)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,26 @@
1
+ module ProconBypassMan
2
+ class Configuration
3
+ module Validator
4
+ # @return [Boolean]
5
+ def valid?
6
+ @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
11
+
12
+ @errors.empty?
13
+ end
14
+
15
+ # @return [Boolean]
16
+ def invalid?
17
+ !valid?
18
+ end
19
+
20
+ # @return [Hash]
21
+ def errors
22
+ @errors ||= Hash.new {|h,k| h[k] = [] }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ class ProconBypassMan::DeviceRegistry
2
+ PROCON_PATH = "/dev/hidraw0"
3
+ PROCON2_PATH = "/dev/hidraw1"
4
+
5
+ def gadget
6
+ @gadget
7
+ end
8
+
9
+ def procon
10
+ @procon
11
+ end
12
+
13
+ def initialize
14
+ init_devices
15
+ end
16
+
17
+ # @return [void]
18
+ def init_devices
19
+ loop do
20
+ case
21
+ when File.exist?(PROCON_PATH)
22
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
23
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
24
+ sleep 0.5
25
+ @gadget = File.open('/dev/hidg0', "w+")
26
+ @procon = File.open(PROCON_PATH, "w+")
27
+ break
28
+ when File.exist?(PROCON2_PATH)
29
+ system('echo > /sys/kernel/config/usb_gadget/procon/UDC')
30
+ system('ls /sys/class/udc > /sys/kernel/config/usb_gadget/procon/UDC')
31
+ sleep 0.5
32
+ @gadget = File.open('/dev/hidg0', "w+")
33
+ @procon = File.open(PROCON2_PATH, "w+")
34
+ break
35
+ else
36
+ puts "プロコンをラズベイに挿してください"
37
+ sleep(1)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,89 @@
1
+ module ProconBypassMan
2
+ class Counter
3
+ attr_accessor :label, :table, :previous_table
4
+
5
+ def initialize(label: )
6
+ self.label = label
7
+ self.table = {}
8
+ self.previous_table = {}
9
+ end
10
+
11
+ # アクティブなバケットは1つだけ
12
+ def record(event_name)
13
+ return unless $is_stable
14
+ key = Time.now.strftime("%S").to_i
15
+ if table[key].nil?
16
+ self.previous_table = table.values.first
17
+ self.table = {}
18
+ table[key] = {}
19
+ end
20
+ if table[key][event_name].nil?
21
+ table[key][event_name] = 1
22
+ else
23
+ table[key][event_name] += 1
24
+ end
25
+ self
26
+ end
27
+
28
+ def formated_previous_table
29
+ t = previous_table.dup
30
+ start_function = t[:start_function] || 0
31
+ end_function = t[:end_function] || 0
32
+ eagain_wait_readable_on_read = t[:eagain_wait_readable_on_read] || 0
33
+ eagain_wait_readable_on_write = t[:eagain_wait_readable_on_write] || 0
34
+ "(#{(end_function / start_function.to_f * 100).floor(1)}%(#{end_function}/#{start_function}), loss: #{eagain_wait_readable_on_read}, #{eagain_wait_readable_on_write})"
35
+ end
36
+ end
37
+
38
+ module IOMonitor
39
+ def self.new(label: )
40
+ counter = Counter.new(label: label)
41
+ @@list << counter
42
+ counter
43
+ end
44
+
45
+ # @return [Array<Counter>]
46
+ def self.targets
47
+ @@list
48
+ end
49
+
50
+ # ここで集計する
51
+ def self.start!
52
+ Thread.start do
53
+ max_output_length = 0
54
+ loop do
55
+ list = @@list.dup
56
+ unless list.all? { |x| x&.previous_table.is_a?(Hash) }
57
+ sleep 0.5
58
+ next
59
+ end
60
+
61
+ s_to_p = list.detect { |x| x.label == "switch -> procon" }
62
+ previous_table = s_to_p&.previous_table.dup
63
+ if previous_table && previous_table.dig(:eagain_wait_readable_on_read) && previous_table.dig(:eagain_wait_readable_on_read) > 300
64
+ # ProconBypassMan.logger.debug { "接続の確立ができません" }
65
+ # Process.kill("USR1", Process.ppid)
66
+ end
67
+
68
+ line = list.map { |counter|
69
+ "#{counter.label}(#{counter.formated_previous_table})"
70
+ }.join(", ")
71
+ max_output_length = line.length
72
+ sleep 0.7
73
+ print "\r"
74
+ print " " * max_output_length
75
+ print "\r"
76
+ print line
77
+ ProconBypassMan.logger.debug { line }
78
+ break if $will_terminate_token
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.reset!
84
+ @@list = []
85
+ end
86
+
87
+ reset!
88
+ end
89
+ end