procon_bypass_man 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|