procon_bypass_man 0.1.23 → 0.2.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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +13 -0
  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 +20 -0
  7. data/Gemfile +6 -3
  8. data/Gemfile.lock +3 -3
  9. data/README.md +2 -2
  10. data/bin/generate_default_app +10 -0
  11. data/docs/getting_started.md +52 -7
  12. data/docs/setup_raspi.md +0 -1
  13. data/lib/procon_bypass_man/background/jobs/base_job.rb +1 -1
  14. data/lib/procon_bypass_man/background/jobs/concerns/{job_runnable.rb → job_performable.rb} +1 -1
  15. data/lib/procon_bypass_man/background.rb +1 -1
  16. data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +1 -1
  17. data/lib/procon_bypass_man/{commands → bypass}/bypass_command.rb +11 -19
  18. data/lib/procon_bypass_man/bypass/usb_hid_logger.rb +0 -1
  19. data/lib/procon_bypass_man/bypass.rb +9 -14
  20. data/lib/procon_bypass_man/commands/print_boot_message_command.rb +1 -2
  21. data/lib/procon_bypass_man/commands/send_error_command.rb +2 -2
  22. data/lib/procon_bypass_man/commands.rb +0 -3
  23. data/lib/procon_bypass_man/configuration.rb +22 -3
  24. data/lib/procon_bypass_man/device_connection/command.rb +28 -0
  25. data/lib/procon_bypass_man/{device_connector.rb → device_connection/executor.rb} +51 -38
  26. data/lib/procon_bypass_man/device_connection/output_report_generator.rb +42 -0
  27. data/lib/procon_bypass_man/device_connection/output_report_markerable.rb +28 -0
  28. data/lib/procon_bypass_man/device_connection/output_report_sub_command_table.rb +133 -0
  29. data/lib/procon_bypass_man/device_connection/output_report_watcher.rb +41 -0
  30. data/lib/procon_bypass_man/device_connection/pre_bypass.rb +67 -0
  31. data/lib/procon_bypass_man/device_connection/procon_setting_overrider.rb +78 -0
  32. data/lib/procon_bypass_man/device_connection/spoofing_output_report_watcher.rb +39 -0
  33. data/lib/procon_bypass_man/device_connection.rb +16 -0
  34. data/lib/procon_bypass_man/device_model.rb +17 -0
  35. data/lib/procon_bypass_man/io_monitor.rb +20 -1
  36. data/lib/procon_bypass_man/procon/macro_builder.rb +5 -3
  37. data/lib/procon_bypass_man/procon_display/bypass_hook.rb +11 -0
  38. data/lib/procon_bypass_man/procon_display/http_request.rb +31 -0
  39. data/lib/procon_bypass_man/procon_display/http_response.rb +23 -0
  40. data/lib/procon_bypass_man/procon_display/server.rb +33 -0
  41. data/lib/procon_bypass_man/procon_display/server_app.rb +17 -0
  42. data/lib/procon_bypass_man/procon_display/status.rb +20 -0
  43. data/lib/procon_bypass_man/procon_display.rb +11 -0
  44. data/lib/procon_bypass_man/{commands → remote_pbm_action/commands}/run_remote_pbm_action_dispatch_command.rb +0 -0
  45. data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +7 -1
  46. data/lib/procon_bypass_man/remote_pbm_action.rb +1 -0
  47. data/lib/procon_bypass_man/runner.rb +4 -7
  48. data/lib/procon_bypass_man/support/callbacks.rb +68 -34
  49. data/lib/procon_bypass_man/support/never_exit_accidentally.rb +3 -3
  50. data/lib/procon_bypass_man/support/usb_device_controller.rb +2 -2
  51. data/lib/procon_bypass_man/support/yaml_loader.rb +12 -0
  52. data/lib/procon_bypass_man/version.rb +1 -1
  53. data/lib/procon_bypass_man/websocket/client.rb +1 -4
  54. data/lib/procon_bypass_man.rb +66 -41
  55. data/procon_bypass_man.gemspec +1 -1
  56. data/project_template/README.md +0 -5
  57. data/project_template/app.rb +24 -6
  58. data/project_template/app.rb.erb +64 -0
  59. data/project_template/lib/app_generator.rb +31 -0
  60. data/project_template/web.rb +1 -1
  61. data/sig/main.rbs +3 -3
  62. data/tmp/.keep +0 -0
  63. metadata +30 -8
  64. data/lib/procon_bypass_man/commands/connect_device_command.rb +0 -18
@@ -1,9 +1,6 @@
1
1
  require "timeout"
2
2
 
3
- class ProconBypassMan::DeviceConnector
4
- class BytesMismatchError < StandardError; end
5
- class NotFoundProconError < StandardError; end
6
-
3
+ class ProconBypassMan::DeviceConnection::Executer
7
4
  class Value
8
5
  attr_accessor :read_from, :values, :call_block_if_receive, :block
9
6
 
@@ -15,33 +12,41 @@ class ProconBypassMan::DeviceConnector
15
12
  end
16
13
  end
17
14
 
18
- def self.connect
19
- s = new(throw_error_if_timeout: true)
20
- s.add([
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: [
21
22
  ["0000"],
22
23
  ["0000"],
23
24
  ["8005"],
24
25
  ["0000"],
25
26
  ], read_from: :switch)
26
27
  # 1. Sends current connection status, and if the Joy-Con are connected,
27
- s.add([["8001"]], read_from: :switch)
28
- s.add([/^8101/], read_from: :procon) # <<< 81010003176d96e7a5480000000, macaddressとコントローラー番号を返す
28
+ s.add(expected_to_receive: [["8001"]], read_from: :switch)
29
+ s.add(expected_to_receive: [/^8101/], read_from: :procon) # <<< 81010003176d96e7a5480000000, macaddressとコントローラー番号を返す
29
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.
30
- s.add([["8002"]], read_from: :switch)
31
- s.add([/^8102/], read_from: :procon)
31
+ s.add(expected_to_receive: [["8002"]], read_from: :switch)
32
+ s.add(expected_to_receive: [/^8102/], read_from: :procon)
32
33
  # 3
33
- s.add([/^0100/], read_from: :switch)
34
- s.add([/^21/], read_from: :procon, call_block_if_receive: /^8101/) do |this|
35
- ProconBypassMan.logger.info "(start special route)"
36
- this.blocking_read_with_timeout_from_procon # <<< 810100032dbd42e9b698000
37
- this.write_to_procon("8002")
38
- this.blocking_read_with_timeout_from_procon # <<< 8102
39
- this.write_to_procon("01000000000000000000033000000000000000000000000000000000000000000000000000000000000000000000000000")
40
- this.blocking_read_with_timeout_from_procon # <<< 21
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
41
46
  end
42
47
 
43
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.
44
- s.add([["8004"]], read_from: :switch)
49
+ s.add(expected_to_receive: [["8004"]], read_from: :switch)
45
50
  s.drain_all
46
51
  return [s.switch, s.procon]
47
52
  end
@@ -53,7 +58,8 @@ class ProconBypassMan::DeviceConnector
53
58
  @throw_error_if_mismatch = throw_error_if_mismatch
54
59
  end
55
60
 
56
- def add(values, read_from: , call_block_if_receive: false, &block)
61
+ def add(expected_to_receive: , read_from: , call_block_if_receive: nil, &block)
62
+ values = expected_to_receive
57
63
  @queue << Value.new(values: values, read_from: read_from, call_block_if_receive: call_block_if_receive, &block)
58
64
  end
59
65
 
@@ -67,6 +73,7 @@ class ProconBypassMan::DeviceConnector
67
73
  item.values.each do |value|
68
74
  raw_data = nil
69
75
  timer = ProconBypassMan::SafeTimeout.new
76
+
70
77
  begin
71
78
  timer.throw_if_timeout!
72
79
  raw_data = from_device(item).read_nonblock(64)
@@ -76,8 +83,11 @@ class ProconBypassMan::DeviceConnector
76
83
  retry
77
84
  end
78
85
 
79
- if item.call_block_if_receive =~ raw_data.unpack("H*").first
80
- raw_data = item.block.call(self)
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
81
91
  end
82
92
 
83
93
  result =
@@ -89,13 +99,14 @@ class ProconBypassMan::DeviceConnector
89
99
  else
90
100
  raise "#{value}は知りません"
91
101
  end
102
+
92
103
  if result
93
- ProconBypassMan.logger.info "OK(expected: #{value}, got: #{raw_data.unpack("H*")})"
94
- debug_log_buffer << "OK(expected: #{value}, got: #{raw_data.unpack("H*")})"
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}"
95
106
  else
96
- ProconBypassMan.logger.info "NG(expected: #{value}, got: #{raw_data.unpack("H*")})"
97
- debug_log_buffer << "NG(expected: #{value}, got: #{raw_data.unpack("H*")})"
98
- raise BytesMismatchError.new(debug_log_buffer) if @throw_error_if_mismatch
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
99
110
  end
100
111
  to_device(item).write_nonblock(raw_data)
101
112
  end
@@ -103,8 +114,8 @@ class ProconBypassMan::DeviceConnector
103
114
  rescue ProconBypassMan::SafeTimeout::Timeout, Timeout::Error => e
104
115
  ProconBypassMan.logger.error "timeoutになりました(#{e.message})"
105
116
  compressed_buffer_text = ProconBypassMan::CompressArray.new(debug_log_buffer).compress.join("\n")
106
- ProconBypassMan::SendErrorCommand.execute(error: compressed_buffer_text)
107
- raise if @throw_error_if_timeout
117
+ ProconBypassMan::SendErrorCommand.execute(error: compressed_buffer_text, stdout: false)
118
+ raise ProconBypassMan::SafeTimeout::Timeout if @throw_error_if_timeout
108
119
  end
109
120
 
110
121
  def from_device(item)
@@ -143,23 +154,25 @@ class ProconBypassMan::DeviceConnector
143
154
  return
144
155
  end
145
156
  ProconBypassMan::UsbDeviceController.init
157
+ ProconBypassMan::UsbDeviceController.reset
146
158
 
147
159
  if path = ProconBypassMan::DeviceProconFinder.find
148
160
  @procon = File.open(path, "w+b")
149
161
  ProconBypassMan.logger.info "proconのデバイスファイルは#{path}を使います"
150
162
  else
151
- raise(ProconBypassMan::DeviceConnector::NotFoundProconError)
163
+ raise(ProconBypassMan::DeviceConnection::NotFoundProconError)
152
164
  end
153
- @gadget = File.open('/dev/hidg0', "w+b")
154
165
 
155
- ProconBypassMan::UsbDeviceController.reset
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
156
174
 
157
175
  @initialized_devices = true
158
- rescue Errno::ENXIO => e
159
- # /dev/hidg0 をopenできないときがある
160
- ProconBypassMan::SendErrorCommand.execute(error: "Errno::ENXIO (No such device or address @ rb_sysopen - /dev/hidg0)が起きました。resetします.\n #{e.full_message}")
161
- ProconBypassMan::UsbDeviceController.reset
162
- retry
163
176
  end
164
177
 
165
178
  def blocking_read_with_timeout_from_procon
@@ -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
@@ -5,9 +5,11 @@ class ProconBypassMan::Procon::MacroBuilder
5
5
  return subjects.first.to_steps
6
6
  end
7
7
 
8
- base = subjects.first
9
- remain = subjects[1..-1]
10
- remain.map { |x| base.to_steps.zip(x.to_steps) }.first
8
+ subjects.inject([[], []]) do |acc, item|
9
+ acc[0] << item.to_steps[0]
10
+ acc[1] << item.to_steps[1]
11
+ acc
12
+ end
11
13
  end
12
14
  end
13
15