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,17 @@
1
+ class ProconBypassMan::Processor
2
+ # @return [String] binary
3
+ def initialize(binary)
4
+ @binary = binary
5
+ end
6
+
7
+ # @return [String] 加工後の入力データ
8
+ def process
9
+ unless @binary[0] == "\x30".b
10
+ return @binary
11
+ end
12
+
13
+ procon = ProconBypassMan::Procon.new(@binary)
14
+ procon.apply!
15
+ procon.to_binary
16
+ end
17
+ end
@@ -0,0 +1,129 @@
1
+ class ProconBypassMan::Procon
2
+ require "procon_bypass_man/procon/mode_registry"
3
+ require "procon_bypass_man/procon/macro_registry"
4
+ require "procon_bypass_man/procon/layer_changeable"
5
+ require "procon_bypass_man/procon/button_collection"
6
+ require "procon_bypass_man/procon/pressed_button_helper"
7
+ require "procon_bypass_man/procon/user_operation"
8
+
9
+ attr_accessor :user_operation
10
+
11
+ def self.reset_cvar!
12
+ @@status = {
13
+ buttons: {},
14
+ current_layer_key: :up,
15
+ ongoing_macro: MacroRegistry.load(:null),
16
+ ongoing_mode: ModeRegistry.load(:manual),
17
+ }
18
+ end
19
+ def self.reset!; reset_cvar!; end
20
+ reset!
21
+
22
+ def initialize(binary)
23
+ self.user_operation = ProconBypassMan::Procon::UserOperation.new(binary.dup)
24
+ end
25
+
26
+ def status; @@status[:buttons]; end
27
+ def ongoing_macro; @@status[:ongoing_macro]; end
28
+ def ongoing_mode; @@status[:ongoing_mode]; end
29
+ def current_layer_key; @@status[:current_layer_key]; end
30
+
31
+ def current_layer
32
+ ProconBypassMan::Configuration.instance.layers[current_layer_key]
33
+ end
34
+
35
+ def apply!
36
+ if user_operation.change_layer?
37
+ @@status[:current_layer_key] = user_operation.next_layer_key if user_operation.pressed_next_layer?
38
+ user_operation.set_no_action!
39
+ return
40
+ end
41
+
42
+ if ongoing_macro.finished?
43
+ current_layer.macros.each do |macro_name, options|
44
+ if options[:if_pressed].all? { |b| user_operation.pressed_button?(b) }
45
+ @@status[:ongoing_macro] = MacroRegistry.load(macro_name)
46
+ end
47
+ end
48
+ end
49
+
50
+ case current_layer.mode
51
+ when :manual
52
+ @@status[:ongoing_mode] = ModeRegistry.load(:manual)
53
+ current_layer.flip_buttons.each do |button, options|
54
+ unless options[:if_pressed]
55
+ status[button] = !status[button]
56
+ next
57
+ end
58
+
59
+ if options[:if_pressed] && options[:if_pressed].all? { |b| user_operation.pressed_button?(b) }
60
+ status[button] = !status[button]
61
+ else
62
+ status[button] = false
63
+ end
64
+ end
65
+ else
66
+ unless ongoing_mode.name == current_layer.mode
67
+ @@status[:ongoing_mode] = ProconBypassMan::Procon::ModeRegistry.load(current_layer.mode)
68
+ end
69
+ if(binary = ongoing_mode.next_binary)
70
+ self.user_operation.merge(target_binary: binary)
71
+ end
72
+ return
73
+ end
74
+
75
+ status
76
+ end
77
+
78
+ # @return [String<binary>]
79
+ def to_binary
80
+ if ongoing_mode.name != :manual
81
+ return user_operation.binary
82
+ end
83
+
84
+ if ongoing_macro.ongoing?
85
+ step = ongoing_macro.next_step or return(user_operation.binary)
86
+ user_operation.press_button_only(step)
87
+ return user_operation.binary
88
+ end
89
+
90
+ current_layer.flip_buttons.each do |button, options|
91
+ # 何もしないで常に連打
92
+ if !options[:if_pressed] && status[button]
93
+ user_operation.press_button(button) unless user_operation.pressed_button?(button)
94
+ next
95
+ end
96
+
97
+ # 押している時だけ連打
98
+ if options[:if_pressed] && options[:if_pressed].all? { |b| user_operation.pressed_button?(b) }
99
+ if !status[button]
100
+ user_operation.unpress_button(button)
101
+ end
102
+ if options[:force_neutral] && user_operation.pressed_button?(options[:force_neutral])
103
+ button = options[:force_neutral]
104
+ user_operation.unpress_button(button)
105
+ end
106
+ end
107
+ end
108
+
109
+ current_layer.remaps.each do |from_button, to_button|
110
+ if user_operation.pressed_button?(from_button)
111
+ user_operation.unpress_button(from_button)
112
+ # TODO 2重でpressしないようにしたい
113
+ user_operation.press_button(to_button) unless user_operation.pressed_button?(to_button)
114
+ end
115
+ end
116
+
117
+ user_operation.binary
118
+ end
119
+
120
+ private
121
+
122
+ def method_missing(name)
123
+ if name.to_s =~ /\Apressed_[a-z]+\?\z/
124
+ user_operation.public_send(name)
125
+ else
126
+ super
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,40 @@
1
+ class ProconBypassMan::Procon::ButtonCollection
2
+ class Button
3
+ attr_accessor :byte_position, :bit_position
4
+ def initialize(key)
5
+ b = BUTTONS_MAP[key] or raise("undefined button")
6
+ self.byte_position = b[:byte_position]
7
+ self.bit_position = b[:bit_position]
8
+ end
9
+ end
10
+
11
+ #3) ZR R SR(right) SL(right) A B X Y
12
+ #4) Grip (none) Cap Home ThumbL ThumbR + -
13
+ #5) ZL L SL(left) SR(left) Left Right Up Down
14
+ #6) analog[0]
15
+ #7) analog[1]
16
+ #8) analog[2]
17
+ #9) analog[3]
18
+ #a) analog[4]
19
+ #b) analog[5]
20
+ BYTES_MAP = {
21
+ 0 => nil,
22
+ 1 => nil,
23
+ 2 => nil,
24
+ 3 => [:zr, :r, :sr, :sl, :a, :b, :x, :y],
25
+ 4 => [:grip, :_undefined_key, :cap, :home, :thumbl, :thumbr, :plus, :minus],
26
+ 5 => [:zl, :l, :sl, :sr, :left, :right, :up, :down],
27
+ }
28
+
29
+ BUTTONS_MAP = BYTES_MAP.reduce({}) { |acc, value|
30
+ next acc if value[1].nil?
31
+ value[1].reverse.each.with_index do |button, index|
32
+ acc[button] = { byte_position: value[0], bit_position: index }
33
+ end
34
+ acc
35
+ }
36
+
37
+ def self.load(button_key)
38
+ Button.new(button_key)
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ class ProconBypassMan::Procon
2
+ module Data
3
+ NO_ACTION = "30f28100800078c77448287509550274ff131029001b0022005a0271ff191028001e00210064027cff1410280020002100000000000000000000000000000000".freeze
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ module ProconBypassMan::Procon::LayerChangeable
2
+ def next_layer_key
3
+ case
4
+ when pressed_up?
5
+ :up
6
+ when pressed_right?
7
+ :right
8
+ when pressed_left?
9
+ :left
10
+ when pressed_down?
11
+ :down
12
+ else
13
+ ProconBypassMan.logger.warn("next_layer_key is unknown")
14
+ :up
15
+ end
16
+ end
17
+
18
+ def change_layer?
19
+ if ProconBypassMan::Configuration.instance.prefix_keys.empty?
20
+ raise "prefix_keysが未設定です"
21
+ end
22
+ ProconBypassMan::Configuration.instance.prefix_keys.map { |b| pressed_button?(b) }.all?
23
+ end
24
+
25
+ def pressed_next_layer?
26
+ change_layer? && (pressed_up? || pressed_right? || pressed_left? || pressed_down?)
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ class ProconBypassMan::Procon::MacroRegistry
2
+ class Macro
3
+ attr_accessor :name, :steps
4
+
5
+ def initialize(name: , steps: )
6
+ self.name = name
7
+ self.steps = steps
8
+ end
9
+
10
+ def next_step
11
+ steps.shift
12
+ end
13
+
14
+ def finished?
15
+ steps.empty?
16
+ end
17
+
18
+ def ongoing?
19
+ !finished?
20
+ end
21
+ end
22
+
23
+ PRESETS = {
24
+ null: [],
25
+ }
26
+
27
+ def self.install_plugin(klass)
28
+ if plugins[klass.name]
29
+ raise "すでに登録済みです"
30
+ end
31
+ plugins[klass.name] = klass.steps
32
+ end
33
+
34
+ def self.load(name)
35
+ steps = PRESETS[name] || plugins[name] || raise("unknown macro")
36
+ Macro.new(name: name, steps: steps.dup)
37
+ end
38
+
39
+ def self.reset!
40
+ ProconBypassMan::Configuration.instance.macro_plugins = {}
41
+ end
42
+
43
+ def self.plugins
44
+ ProconBypassMan::Configuration.instance.macro_plugins
45
+ end
46
+
47
+ reset!
48
+ end
@@ -0,0 +1,46 @@
1
+ class ProconBypassMan::Procon::ModeRegistry
2
+ class Mode
3
+ attr_accessor :name, :binaries, :source_binaries
4
+
5
+ def initialize(name: , binaries: )
6
+ self.name = name
7
+ self.binaries = binaries
8
+ self.source_binaries = binaries.dup
9
+ end
10
+
11
+ def next_binary
12
+ binary = binaries.shift
13
+ unless binary
14
+ self.binaries = source_binaries.dup
15
+ return binaries.shift
16
+ end
17
+ return binary
18
+ end
19
+ end
20
+
21
+ PRESETS = {
22
+ manual: [],
23
+ }
24
+
25
+ def self.install_plugin(klass)
26
+ if plugins[klass.name]
27
+ raise "すでに登録済みです"
28
+ end
29
+ plugins[klass.name] = klass.binaries
30
+ end
31
+
32
+ def self.load(name)
33
+ b = PRESETS[name] || plugins[name] || raise("unknown mode")
34
+ Mode.new(name: name, binaries: b.dup)
35
+ end
36
+
37
+ def self.reset!
38
+ ProconBypassMan::Configuration.instance.mode_plugins = {}
39
+ end
40
+
41
+ def self.plugins
42
+ ProconBypassMan::Configuration.instance.mode_plugins
43
+ end
44
+
45
+ reset!
46
+ end
@@ -0,0 +1,25 @@
1
+ module ProconBypassMan::Procon::PushedButtonHelper
2
+ module Static
3
+ def pressed_button?(button)
4
+ binary[
5
+ ::ProconBypassMan::Procon::ButtonCollection.load(button).byte_position
6
+ ].unpack("H*").first.to_i(16).to_s(2).reverse[
7
+ ::ProconBypassMan::Procon::ButtonCollection.load(button).bit_position
8
+ ] == '1'
9
+ end
10
+ end
11
+
12
+ module Dynamic
13
+ @@compiled = false
14
+ def compile_if_not_compile_yet!
15
+ unless @@compiled
16
+ ::ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP.each do |button, value|
17
+ define_method "pressed_#{button}?" do
18
+ pressed_button?(button)
19
+ end
20
+ end
21
+ end
22
+ @@compiled = true
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ class ProconBypassMan::Procon
2
+ class UserOperation
3
+ include LayerChangeable
4
+ include PushedButtonHelper::Static
5
+ extend PushedButtonHelper::Dynamic
6
+
7
+ attr_reader :binary
8
+
9
+ def initialize(binary)
10
+ self.class.compile_if_not_compile_yet!
11
+ unless binary.encoding.name == ASCII_ENCODING
12
+ raise "おかしいです"
13
+ end
14
+ @binary = binary
15
+ end
16
+
17
+ ZERO_BIT = ["0"].pack("H*").freeze
18
+ ASCII_ENCODING = "ASCII-8BIT"
19
+
20
+ # @depilicate
21
+ def binary=(binary)
22
+ unless binary.encoding.name == ASCII_ENCODING
23
+ raise "おかしいです"
24
+ end
25
+ @binary = binary
26
+ end
27
+
28
+ def set_no_action!
29
+ binary[3] = ZERO_BIT
30
+ binary[4] = ZERO_BIT
31
+ binary[5] = ZERO_BIT
32
+ end
33
+
34
+ def unpress_button(button)
35
+ byte_position = ButtonCollection.load(button).byte_position
36
+ value = binary[byte_position].unpack("H*").first.to_i(16) - 2**ButtonCollection.load(button).bit_position
37
+ binary[byte_position] = ["%02X" % value.to_s].pack("H*")
38
+ end
39
+
40
+ def press_button(button)
41
+ byte_position = ButtonCollection.load(button).byte_position
42
+ value = binary[byte_position].unpack("H*").first.to_i(16) + 2**ButtonCollection.load(button).bit_position
43
+ binary[byte_position] = ["%02X" % value.to_s].pack("H*")
44
+ end
45
+
46
+ def press_button_only(button)
47
+ [ProconBypassMan::Procon::Data::NO_ACTION.dup].pack("H*").tap do |no_action_binary|
48
+ ButtonCollection.load(button).byte_position
49
+ byte_position = ButtonCollection.load(button).byte_position
50
+ value = 2**ButtonCollection.load(button).bit_position
51
+ no_action_binary[byte_position] = ["%02X" % value.to_s].pack("H*")
52
+ binary[3] = no_action_binary[3]
53
+ binary[4] = no_action_binary[4]
54
+ binary[5] = no_action_binary[5]
55
+ end
56
+ end
57
+
58
+ def merge(target_binary: )
59
+ tb = [target_binary].pack("H*")
60
+ binary[3] = tb[3]
61
+ binary[4] = tb[4]
62
+ binary[5] = tb[5]
63
+ binary[6] = tb[6]
64
+ binary[7] = tb[7]
65
+ binary[8] = tb[8]
66
+ binary[9] = tb[9]
67
+ binary[10] = tb[10]
68
+ binary[11] = tb[11]
69
+ self.binary
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,171 @@
1
+ require_relative "io_monitor"
2
+
3
+ class ProconBypassMan::Runner
4
+ class InterruptForRestart < StandardError; end
5
+ class InterruptForCouldNotConnect < StandardError; end
6
+
7
+ def initialize(gadget: , procon: )
8
+ @gadget = gadget
9
+ @procon = procon
10
+
11
+ $will_interval_0_0_0_5 = 0
12
+ $will_interval_1_6 = 0
13
+ end
14
+
15
+ def run
16
+ first_negotiation
17
+ $is_stable = false
18
+
19
+ self_read, self_write = IO.pipe
20
+ %w(TERM INT USR1 USR2).each do |sig|
21
+ begin
22
+ trap sig do
23
+ self_write.puts(sig)
24
+ end
25
+ rescue ArgumentError
26
+ ProconBypassMan.logger.info("Signal #{sig} not supported")
27
+ end
28
+ end
29
+
30
+ FileUtils.mkdir_p "tmp"
31
+ File.write "tmp/pid", $$
32
+
33
+ loop do
34
+ $will_terminate_token = false
35
+ main_loop_pid = fork { main_loop }
36
+
37
+ begin
38
+ while readable_io = IO.select([self_read])
39
+ signal = readable_io.first[0].gets.strip
40
+ handle_signal(signal)
41
+ end
42
+ rescue InterruptForCouldNotConnect
43
+ $will_terminate_token = true
44
+ Process.kill("TERM", main_loop_pid)
45
+ Process.wait
46
+ raise ProconBypassMan::CouldNotConnectDeviceError
47
+ rescue InterruptForRestart
48
+ $will_terminate_token = true
49
+ Process.kill("TERM", main_loop_pid)
50
+ Process.wait
51
+ ProconBypassMan.logger.info("Reloading config file")
52
+ begin
53
+ ProconBypassMan::Configuration::Loader.reload_setting
54
+ puts "設定ファイルの再読み込みができました"
55
+ rescue ProconBypassMan::CouldNotLoadConfigError
56
+ ProconBypassMan.logger.error "設定ファイルが不正です。再読み込みができませんでした"
57
+ end
58
+ ProconBypassMan.logger.info("バイパス処理を再開します")
59
+ rescue Interrupt
60
+ $will_terminate_token = true
61
+ Process.kill("TERM", main_loop_pid)
62
+ Process.wait
63
+ @gadget&.close
64
+ @procon&.close
65
+ exit 1
66
+ end
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def main_loop
73
+ # TODO 接続確立完了をswitchを読み取るようにして、この暫定で接続完了sleepを消す
74
+ Thread.new do
75
+ sleep(10)
76
+ $will_interval_0_0_0_5 = 0.005
77
+ $will_interval_1_6 = 1.6
78
+ $is_stable = true
79
+ end
80
+
81
+ ProconBypassMan::IOMonitor.start!
82
+ # gadget => procon
83
+ # 遅くていい
84
+ monitor1 = ProconBypassMan::IOMonitor.new(label: "switch -> procon")
85
+ monitor2 = ProconBypassMan::IOMonitor.new(label: "procon -> switch")
86
+ ProconBypassMan.logger.info "Thread1を起動します"
87
+ t1 = Thread.new do
88
+ bypass = ProconBypassMan::Bypass.new(gadget: @gadget, procon: @procon, monitor: monitor1)
89
+ begin
90
+ loop do
91
+ break if $will_terminate_token
92
+ bypass.send_gadget_to_procon!
93
+ rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
94
+ ProconBypassMan.logger.error "Proconが切断されました.終了処理を開始します"
95
+ Process.kill "TERM", Process.ppid
96
+ end
97
+ ProconBypassMan.logger.info "Thread1を終了します"
98
+ end
99
+ end
100
+
101
+ # procon => gadget
102
+ # シビア
103
+ ProconBypassMan.logger.info "Thread2を起動します"
104
+ t2 = Thread.new do
105
+ bypass = ProconBypassMan::Bypass.new(gadget: @gadget, procon: @procon, monitor: monitor2)
106
+ begin
107
+ loop do
108
+ break if $will_terminate_token
109
+ bypass.send_procon_to_gadget!
110
+ rescue Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError => e
111
+ ProconBypassMan.logger.error "Proconが切断されました.終了処理を開始します"
112
+ Process.kill "TERM", Process.ppid
113
+ end
114
+ ProconBypassMan.logger.info "Thread2を終了します"
115
+ end
116
+ end
117
+
118
+ self_read, self_write = IO.pipe
119
+ %w(TERM INT).each do |sig|
120
+ begin
121
+ trap sig do
122
+ self_write.puts(sig)
123
+ end
124
+ rescue ArgumentError
125
+ puts "プロセスでSignal #{sig} not supported"
126
+ end
127
+ end
128
+
129
+ ProconBypassMan.logger.info "子プロセスでgraceful shutdownの準備ができました"
130
+ begin
131
+ while readable_io = IO.select([self_read])
132
+ signal = readable_io.first[0].gets.strip
133
+ handle_signal(signal)
134
+ end
135
+ rescue Interrupt
136
+ $will_terminate_token = true
137
+ [t1, t2].each(&:join)
138
+ @gadget&.close
139
+ @procon&.close
140
+ exit 1
141
+ end
142
+ end
143
+
144
+ def first_negotiation
145
+ loop do
146
+ begin
147
+ input = @gadget.read_nonblock(128)
148
+ ProconBypassMan.logger.debug { ">>> #{input.unpack("H*")}" }
149
+ @procon.write_nonblock(input)
150
+ if input[0] == "\x80".b && input[1] == "\x01".b
151
+ ProconBypassMan.logger.info("first negotiation is over")
152
+ break
153
+ end
154
+ break if $will_terminate_token
155
+ rescue IO::EAGAINWaitReadable
156
+ end
157
+ end
158
+ end
159
+
160
+ def handle_signal(sig)
161
+ ProconBypassMan.logger.info "#{$$}で#{sig}を受け取りました"
162
+ case sig
163
+ when 'USR1'
164
+ raise InterruptForCouldNotConnect
165
+ when 'USR2'
166
+ raise InterruptForRestart
167
+ when 'INT', 'TERM'
168
+ raise Interrupt
169
+ end
170
+ end
171
+ end