procon_bypass_man 0.1.2

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.
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