procon_bypass_man 0.1.22 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/.github/workflows/gitleacks.yml +12 -0
  4. data/.github/workflows/release.yml +1 -0
  5. data/.github/workflows/ruby.yml +1 -1
  6. data/CHANGELOG.md +22 -0
  7. data/Gemfile.lock +5 -5
  8. data/docs/getting_started.md +9 -1
  9. data/docs/setup_raspi.md +0 -1
  10. data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +1 -1
  11. data/lib/procon_bypass_man/{commands → bypass}/bypass_command.rb +36 -24
  12. data/lib/procon_bypass_man/bypass/usb_hid_logger.rb +3 -0
  13. data/lib/procon_bypass_man/bypass.rb +40 -23
  14. data/lib/procon_bypass_man/commands/print_boot_message_command.rb +2 -3
  15. data/lib/procon_bypass_man/commands/send_error_command.rb +2 -2
  16. data/lib/procon_bypass_man/commands.rb +0 -3
  17. data/lib/procon_bypass_man/configuration.rb +22 -3
  18. data/lib/procon_bypass_man/device_connection/command.rb +28 -0
  19. data/lib/procon_bypass_man/device_connection/executor.rb +190 -0
  20. data/lib/procon_bypass_man/device_connection/output_report_generator.rb +42 -0
  21. data/lib/procon_bypass_man/device_connection/output_report_markerable.rb +28 -0
  22. data/lib/procon_bypass_man/device_connection/output_report_sub_command_table.rb +133 -0
  23. data/lib/procon_bypass_man/device_connection/output_report_watcher.rb +41 -0
  24. data/lib/procon_bypass_man/device_connection/pre_bypass.rb +67 -0
  25. data/lib/procon_bypass_man/device_connection/procon_setting_overrider.rb +78 -0
  26. data/lib/procon_bypass_man/device_connection/spoofing_output_report_watcher.rb +39 -0
  27. data/lib/procon_bypass_man/device_connection.rb +16 -0
  28. data/lib/procon_bypass_man/device_model.rb +17 -0
  29. data/lib/procon_bypass_man/io_monitor.rb +20 -1
  30. data/lib/procon_bypass_man/procon/macro_builder.rb +5 -3
  31. data/lib/procon_bypass_man/procon/suppress_rumble.rb +13 -0
  32. data/lib/procon_bypass_man/{domains → procon/value_objects}/analog_stick_position.rb +0 -0
  33. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/base.rb +0 -0
  34. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_immutable_binary.rb +0 -0
  35. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/has_mutable_binary.rb +0 -0
  36. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/inbound_procon_binary.rb +5 -0
  37. data/lib/procon_bypass_man/{domains → procon/value_objects}/binary/processing_procon_binary.rb +0 -0
  38. data/lib/procon_bypass_man/procon/value_objects/binary.rb +11 -0
  39. data/lib/procon_bypass_man/{domains → procon/value_objects}/bypass_mode.rb +0 -0
  40. data/lib/procon_bypass_man/procon/value_objects/rumble_binary.rb +18 -0
  41. data/lib/procon_bypass_man/procon.rb +8 -0
  42. data/lib/procon_bypass_man/procon_display/http_request.rb +31 -0
  43. data/lib/procon_bypass_man/procon_display/http_response.rb +23 -0
  44. data/lib/procon_bypass_man/procon_display/server.rb +33 -0
  45. data/lib/procon_bypass_man/procon_display/server_app.rb +17 -0
  46. data/lib/procon_bypass_man/procon_display/status.rb +20 -0
  47. data/lib/procon_bypass_man/procon_display.rb +10 -0
  48. data/lib/procon_bypass_man/{commands → remote_pbm_action/commands}/run_remote_pbm_action_dispatch_command.rb +0 -0
  49. data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +7 -1
  50. data/lib/procon_bypass_man/remote_pbm_action.rb +1 -0
  51. data/lib/procon_bypass_man/runner.rb +10 -13
  52. data/lib/procon_bypass_man/support/cycle_sleep.rb +22 -0
  53. data/lib/procon_bypass_man/support/device_mouse_finder.rb +60 -0
  54. data/lib/procon_bypass_man/{device_procon_finder.rb → support/device_procon_finder.rb} +0 -0
  55. data/lib/procon_bypass_man/support/never_exit_accidentally.rb +3 -3
  56. data/lib/procon_bypass_man/support/safe_timeout.rb +7 -1
  57. data/lib/procon_bypass_man/{usb_device_controller.rb → support/usb_device_controller.rb} +9 -4
  58. data/lib/procon_bypass_man/support/yaml_loader.rb +12 -0
  59. data/lib/procon_bypass_man/version.rb +1 -1
  60. data/lib/procon_bypass_man/websocket/client.rb +1 -4
  61. data/lib/procon_bypass_man.rb +66 -51
  62. data/project_template/app.rb +23 -7
  63. data/sig/main.rbs +4 -16
  64. metadata +37 -16
  65. data/lib/procon_bypass_man/commands/connect_device_command.rb +0 -18
  66. data/lib/procon_bypass_man/device_connector.rb +0 -293
  67. data/lib/procon_bypass_man/domains.rb +0 -13
@@ -0,0 +1,190 @@
1
+ require "timeout"
2
+
3
+ class ProconBypassMan::DeviceConnection::Executer
4
+ class Value
5
+ attr_accessor :read_from, :values, :call_block_if_receive, :block
6
+
7
+ def initialize(values: , read_from: , call_block_if_receive: false, &block)
8
+ @values = values
9
+ @read_from = read_from
10
+ @call_block_if_receive = call_block_if_receive
11
+ @block = block
12
+ end
13
+ end
14
+
15
+ def self.new_with_default_args
16
+ new(throw_error_if_timeout: true)
17
+ end
18
+
19
+ def self.execute!
20
+ s = new_with_default_args
21
+ s.add(expected_to_receive: [
22
+ ["0000"],
23
+ ["0000"],
24
+ ["8005"],
25
+ ["0000"],
26
+ ], read_from: :switch)
27
+ # 1. Sends current connection status, and if the Joy-Con are connected,
28
+ s.add(expected_to_receive: [["8001"]], read_from: :switch)
29
+ s.add(expected_to_receive: [/^8101/], read_from: :procon) # <<< 81010003176d96e7a5480000000, macaddressとコントローラー番号を返す
30
+ # 2. Sends handshaking packets over UART to the Joy-Con or Pro Controller Broadcom chip. This command can only be called once per session.
31
+ s.add(expected_to_receive: [["8002"]], read_from: :switch)
32
+ s.add(expected_to_receive: [/^8102/], read_from: :procon)
33
+ # 3
34
+ s.add(expected_to_receive: [/^0100/], read_from: :switch)
35
+ s.add(expected_to_receive: [/^21/], read_from: :procon, call_block_if_receive: /^8101/) do |this|
36
+ begin
37
+ ProconBypassMan.logger.info "(start special route)"
38
+ this.blocking_read_with_timeout_from_procon # <<< 810100032dbd42e9b698000
39
+ this.write_to_procon("8002")
40
+ this.blocking_read_with_timeout_from_procon # <<< 8102
41
+ this.write_to_procon("01000000000000000000033000000000000000000000000000000000000000000000000000000000000000000000000000")
42
+ this.blocking_read_with_timeout_from_procon # <<< 21
43
+ rescue ProconBypassMan::SafeTimeout::Timeout, Timeout::Error
44
+ raise ProconBypassMan::DeviceConnection::TimeoutErrorInConditionalRoute
45
+ end
46
+ end
47
+
48
+ # 4. Forces the Joy-Con or Pro Controller to only talk over USB HID without any timeouts. This is required for the Pro Controller to not time out and revert to Bluetooth.
49
+ s.add(expected_to_receive: [["8004"]], read_from: :switch)
50
+ s.drain_all
51
+ return [s.switch, s.procon]
52
+ end
53
+
54
+ def initialize(throw_error_if_timeout: false, throw_error_if_mismatch: false)
55
+ @queue = []
56
+ @initialized_devices = false
57
+ @throw_error_if_timeout = throw_error_if_timeout
58
+ @throw_error_if_mismatch = throw_error_if_mismatch
59
+ end
60
+
61
+ def add(expected_to_receive: , read_from: , call_block_if_receive: nil, &block)
62
+ values = expected_to_receive
63
+ @queue << Value.new(values: values, read_from: read_from, call_block_if_receive: call_block_if_receive, &block)
64
+ end
65
+
66
+ def drain_all
67
+ debug_log_buffer = []
68
+ unless @initialized_devices
69
+ init_devices
70
+ end
71
+
72
+ while(item = @queue.shift)
73
+ item.values.each do |value|
74
+ raw_data = nil
75
+ timer = ProconBypassMan::SafeTimeout.new
76
+
77
+ begin
78
+ timer.throw_if_timeout!
79
+ raw_data = from_device(item).read_nonblock(64)
80
+ debug_log_buffer << "read_from(#{item.read_from}): #{raw_data.unpack("H*")}"
81
+ rescue IO::EAGAINWaitReadable
82
+ # debug_log_buffer << "read_from(#{item.read_from}): IO::EAGAINWaitReadable"
83
+ retry
84
+ end
85
+
86
+ if item.call_block_if_receive
87
+ ProconBypassMan.logger.info "call block if receive: #{item.call_block_if_receive}, actual: #{raw_data.unpack("H*")} from: #{item.read_from}"
88
+ if item.call_block_if_receive =~ raw_data.unpack("H*").first
89
+ raw_data = item.block.call(self)
90
+ end
91
+ end
92
+
93
+ result =
94
+ case value
95
+ when String, Array
96
+ value == raw_data.unpack("H*")
97
+ when Regexp
98
+ value =~ raw_data.unpack("H*").first
99
+ else
100
+ raise "#{value}は知りません"
101
+ end
102
+
103
+ if result
104
+ ProconBypassMan.logger.info "OK(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
105
+ debug_log_buffer << "OK(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
106
+ else
107
+ ProconBypassMan.logger.info "NG(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
108
+ debug_log_buffer << "NG(expected: #{value}, got: #{raw_data.unpack("H*")}) from: #{item.read_from}"
109
+ raise ProconBypassMan::DeviceConnection::BytesMismatchError.new(debug_log_buffer) if @throw_error_if_mismatch
110
+ end
111
+ to_device(item).write_nonblock(raw_data)
112
+ end
113
+ end
114
+ rescue ProconBypassMan::SafeTimeout::Timeout, Timeout::Error => e
115
+ ProconBypassMan.logger.error "timeoutになりました(#{e.message})"
116
+ compressed_buffer_text = ProconBypassMan::CompressArray.new(debug_log_buffer).compress.join("\n")
117
+ ProconBypassMan::SendErrorCommand.execute(error: compressed_buffer_text, stdout: false)
118
+ raise ProconBypassMan::SafeTimeout::Timeout if @throw_error_if_timeout
119
+ end
120
+
121
+ def from_device(item)
122
+ case item.read_from
123
+ when :switch
124
+ switch
125
+ when :procon
126
+ procon
127
+ else
128
+ raise
129
+ end
130
+ end
131
+
132
+ # fromの対になる
133
+ def to_device(item)
134
+ case item.read_from
135
+ when :switch
136
+ procon
137
+ when :procon
138
+ switch
139
+ else
140
+ raise
141
+ end
142
+ end
143
+
144
+ def switch
145
+ @gadget
146
+ end
147
+
148
+ def procon
149
+ @procon
150
+ end
151
+
152
+ def init_devices
153
+ if @initialized_devices
154
+ return
155
+ end
156
+ ProconBypassMan::UsbDeviceController.init
157
+ ProconBypassMan::UsbDeviceController.reset
158
+
159
+ if path = ProconBypassMan::DeviceProconFinder.find
160
+ @procon = File.open(path, "w+b")
161
+ ProconBypassMan.logger.info "proconのデバイスファイルは#{path}を使います"
162
+ else
163
+ raise(ProconBypassMan::DeviceConnection::NotFoundProconError)
164
+ end
165
+
166
+ begin
167
+ @gadget = File.open('/dev/hidg0', "w+b")
168
+ rescue Errno::ENXIO => e
169
+ # /dev/hidg0 をopenできないときがある
170
+ ProconBypassMan::SendErrorCommand.execute(error: "Errno::ENXIOが起きたのでresetします.\n #{e.full_message}", stdout: false)
171
+ ProconBypassMan::UsbDeviceController.reset
172
+ retry
173
+ end
174
+
175
+ @initialized_devices = true
176
+ end
177
+
178
+ def blocking_read_with_timeout_from_procon
179
+ Timeout.timeout(4) do
180
+ raw_data = procon.read(64)
181
+ ProconBypassMan.logger.info "<<< #{raw_data.unpack("H*").first}"
182
+ return raw_data
183
+ end
184
+ end
185
+
186
+ def write_to_procon(data)
187
+ ProconBypassMan.logger.info ">>> #{data}"
188
+ procon.write_nonblock([data].pack("H*"))
189
+ end
190
+ end
@@ -0,0 +1,42 @@
1
+ class ProconBypassMan::DeviceConnection::OutputReportGenerator
2
+ def initialize
3
+ @counter = 0
4
+ end
5
+
6
+ # @return [String]
7
+ def generate_by_step(step)
8
+ sub_command_with_arg = ProconBypassMan::DeviceConnection::ProconSettingOverrider::ALL_SETTINGS[step]&.join or raise("Unsupport step")
9
+ raw_data = generate(sub_command_with_arg)
10
+ count_up
11
+ raw_data
12
+ end
13
+
14
+ # @return [String]
15
+ def generate_by_sub_command_with_arg(sub_command_with_arg)
16
+ raw_data = generate(sub_command_with_arg)
17
+ count_up
18
+ raw_data
19
+ end
20
+
21
+ private
22
+
23
+ # @return [String]
24
+ def generate(sub_command_with_arg)
25
+ [
26
+ ["01", counter, "00" * 8, sub_command_with_arg].join
27
+ ].pack("H*")
28
+ end
29
+
30
+ def count_up
31
+ @counter = @counter + 1
32
+ if @counter >= 256
33
+ @counter = 0
34
+ else
35
+ @counter
36
+ end
37
+ end
38
+
39
+ def counter
40
+ @counter.to_s(16).rjust(2, "0")
41
+ end
42
+ end
@@ -0,0 +1,28 @@
1
+ module ProconBypassMan::DeviceConnection::OutputReportMarkerable
2
+ OUTPUT_REPORT_FORMAT = /^01/
3
+ INPUT_REPORT_FORMAT = /^21/
4
+
5
+ # @param [String] raw_data
6
+ # @return [void]
7
+ def mark_as_send(raw_data)
8
+ data = raw_data.unpack("H*").first
9
+ case data
10
+ when OUTPUT_REPORT_FORMAT
11
+ sub_command = data[20..21]
12
+ sub_command_arg = data[22..23]
13
+ @hid_sub_command_request_table.mask_as_send(sub_command: sub_command, sub_command_arg: sub_command_arg)
14
+ end
15
+ end
16
+
17
+ # @param [String] raw_data
18
+ # @return [void]
19
+ def mark_as_receive(raw_data)
20
+ data = raw_data.unpack("H*").first
21
+ case data
22
+ when INPUT_REPORT_FORMAT
23
+ sub_command = data[28..29]
24
+ sub_command_arg = data[30..31]
25
+ @hid_sub_command_request_table.mark_as_receive(sub_command: sub_command, sub_command_arg: sub_command_arg)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,133 @@
1
+ class ProconBypassMan::DeviceConnection::OutputReportSubCommandTable
2
+ class HIDSubCommandResponse
3
+ attr_accessor :sub_command, :sub_command_arg
4
+
5
+ def initialize(sub_command: , sub_command_arg: )
6
+ @sub_command = sub_command
7
+ @sub_command_arg = sub_command_arg
8
+ end
9
+
10
+ def sub_command_with_arg
11
+ case @sub_command
12
+ when *SPECIAL_SUB_COMMANDS
13
+ @sub_command
14
+ else
15
+ if @sub_command_arg
16
+ "#{@sub_command}-#{@sub_command_arg}"
17
+ else
18
+ @sub_command
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ IGNORE_SUB_COMMANDS = {
25
+ "48-01" => true,
26
+ "04-00" => true,
27
+ "10-28" => true, # 返ってこないことがあった
28
+ }
29
+ # レスポンスに引数が含まれない
30
+ SPECIAL_SUB_COMMANDS = [
31
+ "01",
32
+ "02",
33
+ "03",
34
+ "30",
35
+ "38", # home led
36
+ "40",
37
+ "48",
38
+ ]
39
+
40
+ EXPECTED_SUB_COMMANDS = [
41
+ "01-04",
42
+ "02-",
43
+ "04-00",
44
+ "08-00",
45
+ "10-00",
46
+ "10-50",
47
+ "10-80",
48
+ "10-98",
49
+ "10-10",
50
+ "30-",
51
+ "40-",
52
+ "48-", # Enable vibration
53
+ ]
54
+
55
+ def initialize
56
+ @table = {}
57
+ end
58
+
59
+ # @param [String] sub_command
60
+ # @param [String] sub_command_arg
61
+ # @return [void]
62
+ def mask_as_send(sub_command: , sub_command_arg: )
63
+ case sub_command
64
+ when *SPECIAL_SUB_COMMANDS
65
+ @table[sub_command] = false
66
+ else
67
+ response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
68
+ @table[response.sub_command_with_arg] = false
69
+ end
70
+ end
71
+
72
+ # @param [String] sub_command
73
+ # @param [String] sub_command_arg
74
+ # @return [void]
75
+ def mark_as_receive(sub_command: , sub_command_arg: )
76
+ response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
77
+ if @table.key?(response.sub_command_with_arg)
78
+ @table[response.sub_command_with_arg] = true
79
+ end
80
+ end
81
+
82
+ # @param [String] sub_command
83
+ # @param [String] sub_command_arg
84
+ # @return [Boolean]
85
+ def has_key?(sub_command: , sub_command_arg: )
86
+ if IGNORE_SUB_COMMANDS["#{sub_command}-#{sub_command_arg}"]
87
+ return true
88
+ end
89
+
90
+ case sub_command
91
+ when *SPECIAL_SUB_COMMANDS
92
+ @table.key?(sub_command)
93
+ else
94
+ response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
95
+ @table.key?(response.sub_command_with_arg)
96
+ end
97
+ end
98
+
99
+ # @param [String] sub_command
100
+ # @param [String] sub_command_arg
101
+ # @return [Boolean]
102
+ def has_value?(sub_command: , sub_command_arg: )
103
+ if IGNORE_SUB_COMMANDS["#{sub_command}-#{sub_command_arg}"]
104
+ return true
105
+ end
106
+
107
+ response = HIDSubCommandResponse.new(sub_command: sub_command, sub_command_arg: sub_command_arg)
108
+ !!@table[response.sub_command_with_arg]
109
+ end
110
+
111
+ # @return [Boolean]
112
+ def has_unreceived_command?
113
+ !@table.values.all?(&:itself)
114
+ end
115
+
116
+ # @return [String, NilClass]
117
+ def unreceived_sub_command_with_arg
118
+ sub_command = @table.detect { |_key, value| !value }&.first
119
+ if(arg = ProconBypassMan::DeviceConnection::ProconSettingOverrider::SPECIAL_SUB_COMMAND_ARGS[sub_command])
120
+ "#{sub_command}#{arg}"
121
+ else
122
+ sub_command
123
+ end
124
+ end
125
+
126
+ # @return [Boolean]
127
+ def completed?
128
+ EXPECTED_SUB_COMMANDS.all? do |key|
129
+ sub_command, sub_command_arg = key.split("-")
130
+ has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,41 @@
1
+ class ProconBypassMan::DeviceConnection::OutputReportWatcher
2
+ include ProconBypassMan::DeviceConnection::OutputReportMarkerable
3
+
4
+ def initialize
5
+ @hid_sub_command_request_table = ProconBypassMan::DeviceConnection::OutputReportSubCommandTable.new
6
+ @timer = ProconBypassMan::SafeTimeout.new
7
+ end
8
+
9
+ # @param [String] sub_command
10
+ # @param [String] sub_command_arg
11
+ # @return [Boolean]
12
+ def sent?(sub_command: , sub_command_arg: )
13
+ @hid_sub_command_request_table.has_key?(sub_command: sub_command, sub_command_arg: sub_command_arg)
14
+ end
15
+
16
+ # @param [String] sub_command
17
+ # @param [String] sub_command_arg
18
+ # @return [Boolean]
19
+ def received?(sub_command: , sub_command_arg: )
20
+ @hid_sub_command_request_table.has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
21
+ end
22
+
23
+ # @return [Boolean]
24
+ def completed?
25
+ @hid_sub_command_request_table.completed?
26
+ end
27
+
28
+ # @return [Boolean]
29
+ # @raise [Timeout::Error]
30
+ def timeout_or_completed?
31
+ if @timer.timeout?
32
+ ProconBypassMan::SendErrorCommand.execute(error: "[pre_bypass] pre_bypassフェーズがタイムアウトしました")
33
+ return true
34
+ end
35
+
36
+ if completed?
37
+ ProconBypassMan.logger.info "[pre_bypass] pre_bypassフェーズが想定通り終了しました"
38
+ return true
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,67 @@
1
+ class ProconBypassMan::DeviceConnection::PreBypass
2
+ attr_accessor :gadget, :procon, :output_report_watcher
3
+
4
+ def initialize(gadget: , procon: )
5
+ self.gadget = ProconBypassMan::DeviceModel.new(gadget)
6
+ self.procon = ProconBypassMan::DeviceModel.new(procon)
7
+ self.output_report_watcher = ProconBypassMan::DeviceConnection::OutputReportWatcher.new
8
+ end
9
+
10
+ # @return [void]
11
+ def execute!
12
+ loop do
13
+ run_once
14
+
15
+ if output_report_watcher.timeout_or_completed?
16
+ break
17
+ end
18
+ end
19
+ end
20
+
21
+ # @return [void]
22
+ def run_once
23
+ begin
24
+ raw_data = non_blocking_read_switch
25
+ output_report_watcher.mark_as_send(raw_data)
26
+ ProconBypassMan.logger.info "[pre_bypass] >>> #{raw_data.unpack("H*").first}"
27
+ send_procon(raw_data)
28
+ rescue IO::EAGAINWaitReadable
29
+ # no-op
30
+ end
31
+
32
+ 3.times do
33
+ begin
34
+ raw_data = non_blocking_read_procon
35
+ output_report_watcher.mark_as_receive(raw_data)
36
+ ProconBypassMan.logger.info "[pre_bypass] <<< #{raw_data.unpack("H*").first}"
37
+ send_switch(raw_data)
38
+ rescue IO::EAGAINWaitReadable
39
+ # no-op
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # @raise [IO::EAGAINWaitReadable]
47
+ # @return [String]
48
+ def non_blocking_read_switch
49
+ gadget.non_blocking_read
50
+ end
51
+
52
+ # @raise [IO::EAGAINWaitReadable]
53
+ # @return [String]
54
+ def non_blocking_read_procon
55
+ procon.non_blocking_read
56
+ end
57
+
58
+ # @return [void]
59
+ def send_procon(raw_data)
60
+ procon.send(raw_data)
61
+ end
62
+
63
+ # @return [void]
64
+ def send_switch(raw_data)
65
+ gadget.send(raw_data)
66
+ end
67
+ end
@@ -0,0 +1,78 @@
1
+ class ProconBypassMan::DeviceConnection::ProconSettingOverrider
2
+ attr_accessor :procon, :output_report_watcher, :output_report_generator
3
+
4
+ SUB_COMMAND_HOME_LED_ON = "38"
5
+ SUB_COMMAND_ARG_HOME_LED_ON = "1FF0FF"
6
+ SPECIAL_SUB_COMMAND_ARGS = {
7
+ SUB_COMMAND_HOME_LED_ON => SUB_COMMAND_ARG_HOME_LED_ON,
8
+ }
9
+ SETTING_HOME_LED_ON = { home_led_on: [SUB_COMMAND_HOME_LED_ON, SUB_COMMAND_ARG_HOME_LED_ON] }
10
+ ALL_SETTINGS = SETTING_HOME_LED_ON
11
+
12
+ def initialize(procon: )
13
+ use_steps = {}
14
+ if ProconBypassMan.config.enable_home_led_on_connect
15
+ use_steps.merge!(SETTING_HOME_LED_ON)
16
+ end
17
+
18
+ @setting_steps = use_steps.keys
19
+ self.procon = ProconBypassMan::DeviceModel.new(procon)
20
+ self.output_report_watcher = ProconBypassMan::DeviceConnection::SpoofingOutputReportWatcher.new(expected_sub_commands: use_steps.values)
21
+ self.output_report_generator = ProconBypassMan::DeviceConnection::OutputReportGenerator.new
22
+ end
23
+
24
+ def execute!
25
+ loop do
26
+ run_once
27
+
28
+ if output_report_watcher.timeout_or_completed?
29
+ break
30
+ end
31
+ end
32
+ end
33
+
34
+ def run_once
35
+ begin
36
+ raw_data = non_blocking_read_procon
37
+ rescue IO::EAGAINWaitReadable
38
+ return
39
+ end
40
+
41
+ ProconBypassMan.logger.info "[procon_setting_overrider] <<< #{raw_data.unpack("H*").first}"
42
+ output_report_watcher.mark_as_receive(raw_data)
43
+ if output_report_watcher.has_unreceived_command?
44
+ re_override_setting_by_cmd(output_report_watcher.unreceived_sub_command_with_arg)
45
+ else
46
+ if(setting_step = @setting_steps.shift)
47
+ override_setting_by_step(setting_step)
48
+ else
49
+ return
50
+ end
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ # @return [void]
57
+ def override_setting_by_step(setting_step)
58
+ raw_data = output_report_generator.generate_by_step(setting_step)
59
+ ProconBypassMan.logger.info "[procon_setting_overrider] >>> #{raw_data.unpack("H*").first}"
60
+ output_report_watcher.mark_as_send(raw_data)
61
+ send_procon(raw_data)
62
+ end
63
+
64
+ # @return [void]
65
+ def re_override_setting_by_cmd(sub_command_with_arg)
66
+ raw_data = output_report_generator.generate_by_sub_command_with_arg(sub_command_with_arg)
67
+ ProconBypassMan.logger.info "[procon_setting_overrider] >>> #{raw_data.unpack("H*").first}"
68
+ send_procon(raw_data)
69
+ end
70
+
71
+ def non_blocking_read_procon
72
+ procon.non_blocking_read
73
+ end
74
+
75
+ def send_procon(raw_data)
76
+ procon.send(raw_data)
77
+ end
78
+ end
@@ -0,0 +1,39 @@
1
+ class ProconBypassMan::DeviceConnection::SpoofingOutputReportWatcher
2
+ include ProconBypassMan::DeviceConnection::OutputReportMarkerable
3
+
4
+ def initialize(expected_sub_commands: )
5
+ @timer = ProconBypassMan::SafeTimeout.new
6
+ @hid_sub_command_request_table = ProconBypassMan::DeviceConnection::OutputReportSubCommandTable.new
7
+ @expected_sub_commands = expected_sub_commands
8
+ end
9
+
10
+ # @return [Boolean]
11
+ def has_unreceived_command?
12
+ @hid_sub_command_request_table.has_unreceived_command?
13
+ end
14
+
15
+ # @return [String, NilClass]
16
+ def unreceived_sub_command_with_arg
17
+ @hid_sub_command_request_table.unreceived_sub_command_with_arg
18
+ end
19
+
20
+ # @return [Boolean]
21
+ def completed?
22
+ @expected_sub_commands.all? do |sub_command, sub_command_arg|
23
+ @hid_sub_command_request_table.has_value?(sub_command: sub_command, sub_command_arg: sub_command_arg)
24
+ end
25
+ end
26
+
27
+ # @return [Boolean]
28
+ def timeout_or_completed?
29
+ if @timer.timeout?
30
+ ProconBypassMan.logger.info "[procon setting override] プロコンの設定上書き処理がタイムアウトしました"
31
+ return true
32
+ end
33
+
34
+ if completed?
35
+ ProconBypassMan.logger.info "[procon setting override] プロコンの設定上書き処理が想定通り終了しました"
36
+ return true
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ module ProconBypassMan::DeviceConnection
2
+ class BytesMismatchError < StandardError; end
3
+ class NotFoundProconError < StandardError; end
4
+ class TimeoutErrorInConditionalRoute < StandardError; end
5
+ class TimeoutError < StandardError; end
6
+ end
7
+
8
+ require_relative "device_connection/executor"
9
+ require_relative "device_connection/pre_bypass"
10
+ require_relative "device_connection/command"
11
+ require_relative "device_connection/output_report_markerable"
12
+ require_relative "device_connection/procon_setting_overrider"
13
+ require_relative "device_connection/output_report_generator"
14
+ require_relative "device_connection/output_report_sub_command_table"
15
+ require_relative "device_connection/spoofing_output_report_watcher"
16
+ require_relative "device_connection/output_report_watcher"
@@ -0,0 +1,17 @@
1
+ class ProconBypassMan::DeviceModel
2
+ # @param [File] device
3
+ def initialize(device)
4
+ @device = device
5
+ end
6
+
7
+ # @param [String] raw_data
8
+ def send(raw_data)
9
+ @device.write_nonblock(raw_data)
10
+ end
11
+
12
+ # @raise [IO::EAGAINWaitReadable]
13
+ # @return [String]
14
+ def non_blocking_read
15
+ @device.read_nonblock(64)
16
+ end
17
+ end
@@ -1,4 +1,15 @@
1
1
  module ProconBypassMan
2
+ class NullCounter
3
+ def initialize(label: )
4
+ end
5
+
6
+ def record(_)
7
+ end
8
+
9
+ def shutdown
10
+ end
11
+ end
12
+
2
13
  class Counter
3
14
  attr_accessor :label, :table, :previous_table, :active
4
15
 
@@ -40,7 +51,11 @@ module ProconBypassMan
40
51
  end
41
52
 
42
53
  module IOMonitor
54
+ @@thread = nil
55
+
43
56
  def self.new(label: )
57
+ return NullCounter.new(label: label) if not started?
58
+
44
59
  counter = Counter.new(label: label)
45
60
  @@list << counter
46
61
  counter
@@ -51,9 +66,13 @@ module ProconBypassMan
51
66
  @@list
52
67
  end
53
68
 
69
+ def self.started?
70
+ !!@@thread
71
+ end
72
+
54
73
  # ここで集計する
55
74
  def self.start!
56
- Thread.start do
75
+ @@thread = Thread.start do
57
76
  max_output_length = 0
58
77
  loop do
59
78
  list = @@list.select(&:active).dup