procon_bypass_man 0.1.23 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
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