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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +18 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +157 -0
- data/Rakefile +4 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docs/how_to_connect_procon.md +23 -0
- data/docs/setup_raspi.md +38 -0
- data/examples/practical/app.rb +16 -0
- data/examples/practical/setting.yml +24 -0
- data/examples/simple.rb +13 -0
- data/lib/procon_bypass_man.rb +66 -0
- data/lib/procon_bypass_man/bypass.rb +53 -0
- data/lib/procon_bypass_man/configuration.rb +83 -0
- data/lib/procon_bypass_man/configuration/layer.rb +52 -0
- data/lib/procon_bypass_man/configuration/loader.rb +44 -0
- data/lib/procon_bypass_man/configuration/validator.rb +26 -0
- data/lib/procon_bypass_man/device_registry.rb +41 -0
- data/lib/procon_bypass_man/io_monitor.rb +89 -0
- data/lib/procon_bypass_man/processor.rb +17 -0
- data/lib/procon_bypass_man/procon.rb +129 -0
- data/lib/procon_bypass_man/procon/button_collection.rb +40 -0
- data/lib/procon_bypass_man/procon/data.rb +5 -0
- data/lib/procon_bypass_man/procon/layer_changeable.rb +28 -0
- data/lib/procon_bypass_man/procon/macro_registry.rb +48 -0
- data/lib/procon_bypass_man/procon/mode_registry.rb +46 -0
- data/lib/procon_bypass_man/procon/pressed_button_helper.rb +25 -0
- data/lib/procon_bypass_man/procon/user_operation.rb +72 -0
- data/lib/procon_bypass_man/runner.rb +171 -0
- data/lib/procon_bypass_man/version.rb +5 -0
- data/procon_bypass_man.gemspec +35 -0
- 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
|
data/examples/simple.rb
ADDED
@@ -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
|