procon_bypass_man 0.1.22 → 0.2.1

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