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