procon_bypass_man 0.3.6 → 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +1 -0
  5. data/Gemfile.lock +3 -1
  6. data/bin/console +3 -0
  7. data/bin/validate_external_input +19 -0
  8. data/docs/getting_started.md +5 -0
  9. data/docs/setting/integration_external_input_serial_port.md +56 -0
  10. data/docs/setting/integration_external_input_serial_port_format.md +36 -0
  11. data/lib/procon_bypass_man/background/jobs/report_procon_performance_measurements_job.rb +1 -0
  12. data/lib/procon_bypass_man/bypass/procon_to_switch.rb +16 -1
  13. data/lib/procon_bypass_man/commands/print_boot_message_command.rb +2 -0
  14. data/lib/procon_bypass_man/configuration.rb +6 -0
  15. data/lib/procon_bypass_man/external_input/channels/base.rb +12 -0
  16. data/lib/procon_bypass_man/external_input/channels/serial_port_channel.rb +48 -0
  17. data/lib/procon_bypass_man/external_input/channels/tcpip.rb +16 -0
  18. data/lib/procon_bypass_man/external_input/channels.rb +12 -0
  19. data/lib/procon_bypass_man/external_input/external_data.rb +79 -0
  20. data/lib/procon_bypass_man/external_input.rb +35 -0
  21. data/lib/procon_bypass_man/processor.rb +3 -2
  22. data/lib/procon_bypass_man/procon/button_collection.rb +5 -0
  23. data/lib/procon_bypass_man/procon/performance_measurement/measurements_summarizer.rb +4 -0
  24. data/lib/procon_bypass_man/procon/performance_measurement.rb +7 -1
  25. data/lib/procon_bypass_man/procon.rb +17 -2
  26. data/lib/procon_bypass_man/procon_display/status.rb +0 -2
  27. data/lib/procon_bypass_man/support/retryable.rb +4 -2
  28. data/lib/procon_bypass_man/support/web_connectivity_checker.rb +39 -0
  29. data/lib/procon_bypass_man/version.rb +1 -1
  30. data/lib/procon_bypass_man.rb +7 -1
  31. data/project_template/app.rb +10 -1
  32. data/project_template/app.rb.erb +11 -1
  33. metadata +12 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f19608c134a980243f4e45da1a1685367dd4bffc114482682b5d8d6bef03a90
4
- data.tar.gz: c0ce801d735e63e5d6a385854ad83abb08fe044342bda63b5d70d2d50b3589f4
3
+ metadata.gz: 86e705d2907d51077191585d2cd16fb198a8a55226613d25f44b15b5e58f8806
4
+ data.tar.gz: a13290f301295913bbc30a06a1e06689dc5aae328cadd39ed1e91982ff7586d2
5
5
  SHA512:
6
- metadata.gz: 11e84f9b7fa368adc1cbbb8593aca61d2a1a362453a13c5b226912d490c82933d476072f11ecb36226657107aa68b08be734ac19ec8901b56a00deb980d218b9
7
- data.tar.gz: 642a6813619dccbb9d28f634bb7bb866ec92a2ed34fac254336562ed462512b1e9eb6ec2672dae17727e9c6790651f4e481e24a9a919dbd87381e4a457c62796
6
+ metadata.gz: 30618560c54d7c7a730bf97d74b601fce287d6900ed3eaf3c54b1071349b8a7d8b9e346c9099f6dbfad25a568710c5aaa4ce09ad4b12805a2f6d2d3a8134b27f
7
+ data.tar.gz: 96c796ecbbfa544a0427537a50ea17a0b7f10eee360bc6b072872c7621627dccf5403e78bdc2b580e5957fc5758f9b58979ef1f7f341da1a58b849a693ae79f8
data/.circleci/config.yml CHANGED
@@ -119,6 +119,7 @@ build_jobs: &build_jobs
119
119
  - "3.0.1"
120
120
  - "3.0.2"
121
121
  - "3.1.2"
122
+ - "3.2.2"
122
123
  - bundle_install:
123
124
  matrix:
124
125
  parameters:
@@ -127,6 +128,7 @@ build_jobs: &build_jobs
127
128
  - "3.0.1"
128
129
  - "3.0.2"
129
130
  - "3.1.2"
131
+ - "3.2.2"
130
132
  - lint:
131
133
  matrix:
132
134
  parameters:
@@ -135,6 +137,7 @@ build_jobs: &build_jobs
135
137
  - "3.0.1"
136
138
  - "3.0.2"
137
139
  - "3.1.2"
140
+ - "3.2.2"
138
141
  requires:
139
142
  - bundle_install
140
143
  - type_check:
@@ -154,6 +157,7 @@ build_jobs: &build_jobs
154
157
  - "3.0.1"
155
158
  - "3.0.2"
156
159
  - "3.1.2"
160
+ - "3.2.2"
157
161
  requires:
158
162
  - bundle_install
159
163
  workflows:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.3.7] 2023-04-06
2
+ * PBM-Cloudと連携しているかをbootメッセージに表示ようになりました
3
+ * シリアルポート経由でPBMに入力ができるようになりました
4
+
1
5
  ## [0.3.6] 2023-03-12
2
6
  - プロコンの入力状態をpbm-cloudサーバに送信できるようになりました
3
7
 
data/Gemfile CHANGED
@@ -13,6 +13,7 @@ gem "rubocop", require: false
13
13
  gem "sinatra", require: false
14
14
  gem "webrick", require: false
15
15
  gem "stackprof", require: false
16
+ gem "serialport" # シリアル通信をする時に必要。通常はいらない
16
17
 
17
18
  if Gem::Version.new(RUBY_VERSION) > Gem::Version.new("2.6.0")
18
19
  gem 'typeprof', require: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- procon_bypass_man (0.3.6)
4
+ procon_bypass_man (0.3.7)
5
5
  action_cable_client
6
6
  blue_green_process (= 0.1.4.2)
7
7
  pbmenv (>= 0.1.11)
@@ -80,6 +80,7 @@ GEM
80
80
  parser (>= 3.0.1.1)
81
81
  ruby-progressbar (1.11.0)
82
82
  ruby2_keywords (0.0.5)
83
+ serialport (1.3.2)
83
84
  set (1.0.3)
84
85
  sinatra (2.2.0)
85
86
  mustermann (~> 1.0)
@@ -134,6 +135,7 @@ DEPENDENCIES
134
135
  rbs
135
136
  rspec
136
137
  rubocop
138
+ serialport
137
139
  sinatra
138
140
  stackprof
139
141
  steep
data/bin/console CHANGED
@@ -14,6 +14,9 @@ require "pry"
14
14
 
15
15
  require "irb"
16
16
 
17
+ # TODO: replのときチェックが入らないので、書く詳細な実装に触れないようにする。PBM_ENVを参照するなどをする
18
+ ProconBypassMan::ExternalInput.prepare_channels
19
+
17
20
  ProconBypassMan.config.logger = Logger.new($stdout)
18
21
 
19
22
  IRB.start(__FILE__)
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Usage:
4
+ # NOTE: JSON形式の文字列がどういう形で読み込まれるかを確認できます
5
+ # $ echo '{"hex":"hogehoge", "buttons": ["a","b", "zr"]}' | bin/validate_external_input
6
+ # => 読み取った値: {:hex=>"hogehoge", :buttons=>["a", "b", "zr"]}
7
+
8
+ require "bundler/setup"
9
+ require "json"
10
+ require "procon_bypass_man"
11
+ require "procon_bypass_man/external_input"
12
+
13
+ begin
14
+ json_str = ARGF.read
15
+ external_data = ProconBypassMan::ExternalInput::ExternalData.parse!(json_str)
16
+ puts("読み取った値: #{{ hex: external_data.hex, buttons: external_data.buttons }}")
17
+ rescue JSON::ParserError => e
18
+ puts "failed to parse JSON: #{e.message}"
19
+ end
@@ -18,6 +18,7 @@
18
18
  * [設定ファイルの書き方がわからない、エラーが起きるとき](#設定ファイルの書き方がわからない、エラーが起きるとき)
19
19
  * [procon_bypass_manのアップグレード方法](#procon_bypass_manのアップグレード方法)
20
20
  * [procon_bypass_man_cloudについて](#procon_bypass_man_cloudについて)
21
+ * [シリアルポート・GPIOから読み取る](#シリアルポートから読み取る)
21
22
  * [最適化について](#最適化について)
22
23
 
23
24
  ## はじめに
@@ -251,6 +252,10 @@ procon_bypass_man_cloudとの接続が完了後、Raspberry Piを起動時にpro
251
252
 
252
253
  セットアップ方法は https://pbm-cloud.jiikko.com/faq に書いています。
253
254
 
255
+ ## シリアルポートから読み取る
256
+ * [ラズベリーパイのシリアルポート(GPIO)へ書き込んでPBM経由してSwitchへ入力をする方法](/docs/setting/integration_external_input_serial_port.md)
257
+ * [ラズベリーパイのシリアルポート(GPIO)に書き込むフォーマットについて](/docs/setting/integration_external_input_serial_port_format.md)
258
+
254
259
  ## 最適化について
255
260
  本稿では、Rubyの最適化について書きます。上級者向けです。適用しなくても普通に動きますが、逆に適用したことで何らかのケースで遅くなる場合があるかもしれません。
256
261
 
@@ -0,0 +1,56 @@
1
+ # ラズベリーパイのシリアルポート(GPIO)へ書き込んでPBM経由してSwitchへ入力をする方法
2
+ * procon_bypass_man: 0.3.7以上が必要です
3
+ * GPIOからSwitchへ入力できます。本テキストでは設定方法を記載します
4
+
5
+ ## 1. シリアルポートへ書き込みができるようにラズベリーパイのセットアップする
6
+ https://toki-blog.com/pi-serial/ の「汎用シリアルとして使う」 を実施してください
7
+
8
+ ## 2. GPIOへケーブルを挿す
9
+ 対応しているケーブルをGPIOとPCに接続してください。
10
+
11
+ ## 3. PBMのapp.rbを編集し、シリアルポートから読み出せるようにPBMのapp.rbを編集する
12
+ `gemの追加`と`デバイスファイルを指す修正` の2つ必要です。
13
+
14
+ * 1) シリアルポートから読み出すために追加でgemが必要です。 `app.rb` に `gem "serialport"` を追加してください。
15
+
16
+ ```diff
17
+ gemfile do
18
+ source 'https://rubygems.org'
19
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
20
+ gem 'procon_bypass_man', '0.3.7'
21
+ + gem "serialport"
22
+ end
23
+ ```
24
+
25
+ * 2) 以下を参考にして`config.external_input_channels`の行を追加してください。
26
+
27
+ ```diff
28
+ ProconBypassMan.configure do |config|
29
+ config.root = File.expand_path(__dir__)
30
+ config.logger = Logger.new("#{ProconBypassMan.root}/app.log", 1, 1024 * 1024 * 1)
31
+ config.logger.level = :debug
32
+
33
+ # バイパスするログを全部app.logに流すか
34
+ config.verbose_bypass_log = false
35
+
36
+ # webからProconBypassManを操作できるwebサービスと連携します
37
+ # 連携中はエラーログ、パフォーマンスに関するメトリクスを送信します
38
+ # config.api_servers = 'https://pbm-cloud.jiikko.com'
39
+
40
+ # エラーが起きたらerror.logに書き込みます
41
+ config.enable_critical_error_logging = true
42
+
43
+ # pbm-cloudで使う場合はnever_exitにtrueをセットしてください. trueがセットされている場合、不慮の事故が発生してもプロセスが終了しなくなります
44
+ config.never_exit_accidentally = true
45
+
46
+ # 接続に成功したらコントローラーのHOME LEDを光らせるか
47
+ config.enable_home_led_on_connect = true
48
+
49
+ + config.external_input_channels = [
50
+ + ProconBypassMan::ExternalInput::Channels::SerialPortChannel.new(device_path: '/dev/serial0', baud_rate: 9600),
51
+ + ]
52
+ end
53
+ ```
54
+
55
+ 以上で設定は完了です。PBMを起動し、連携ツールからGPIOへ書き込んでください。
56
+ 書き込みフォーマットについては [ラズベリーパイのシリアルポート(GPIO)に書き込むフォーマットについて](/docs/setting/integration_external_input_serial_port_format.md) を参照してください。
@@ -0,0 +1,36 @@
1
+ # ラズベリーパイのシリアルポート(GPIO)に書き込むフォーマットについて
2
+ * procon_bypass_man: 0.3.7からGPIOを経由してSwitchへ入力できるようになりました。本テキストではPBMが読み取れるフォーマットについて記載します
3
+
4
+ ## フォーマット
5
+ JSON形式, plain textに対応しています
6
+
7
+ * JSON形式
8
+ * 受付可能なカラム
9
+ * hex
10
+ * 16進数でエンコードした`INPUT 0x30` を書いてください。スティック操作を除くボタン部分のみが読み込まれます
11
+ * https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/ac8093c84194b3232acb675ac1accce9bcb456a3/bluetooth_hid_notes.md#input-0x30
12
+ * ex: '30f28100800078c77448287509550274ff131029001b0022005a0271ff191028001e00210064027cff1410280020002100000000000000000000000000000000'
13
+ * buttons
14
+ * 「入力したいボタン」「入力したくないボタン」を配列で渡してください
15
+ * 「入力したいボタン」
16
+ * `"y", "x", "b", "a", "sl", "sr", "r", "zr", "minus", "plus", "thumbr", "thumbl", "home", "cap", "down", "up", "right", "left", "l", "zl"`
17
+ * 「入力したくないボタン」
18
+ * `"uny", "unx", "unb", "una", "unsl", "unsr", "unr", "unzr", "unminus", "unplus", "unthumbr", "unthumbl", "unhome", "uncap", "undown", "unup", "unright", "unleft", "unl", "unzl"`
19
+ * ex: `['a', 'b']`
20
+ * 仕様
21
+ * JSONにhex, buttonsの両方が含まれている場合、hexを優先し、buttonsは無視します
22
+ * JSONの例
23
+ * `'{"hex":"30f28100800078c77448287509550274ff131029001b0022005a0271ff191028001e00210064027cff1410280020002100000000000000000000000000000000", "buttons": ["a"] }'`
24
+ * plain text
25
+ * コロンで囲ったボタンを1つの入力として解釈します
26
+ * `:a::b::uny:` を入力した場合には、a, b, uny を読み込みます
27
+
28
+ ## 正しいJSONかを検証する
29
+ 本リポジトリにJSONを検証する `bin/validate_external_input` を同梱しています。実行例を以下に示します。
30
+
31
+ ```shell
32
+ $ echo '{"hex":"30f28100800078c77448287509550274ff131029001b0022005a0271ff191028001e00210064027cff1410280020002100000000000000000000000000000000", "buttons": ["a"] }' | bin/validate_external_input
33
+ 読み取った値: {:hex=>"30f28100800078c77448287509550274ff131029001b0022005a0271ff191028001e00210064027cff1410280020002100000000000000000000000000000000", :buttons=>[:a]}
34
+ ```
35
+
36
+ 以上。
@@ -21,6 +21,7 @@ class ProconBypassMan::ReportProconPerformanceMeasurementsJob < ProconBypassMan:
21
21
  time_taken_p50: metric.time_taken_p50,
22
22
  time_taken_p95: metric.time_taken_p95,
23
23
  time_taken_p99: metric.time_taken_p99,
24
+ external_input_time_max: metric.external_input_time_max,
24
25
  read_error_count: metric.read_error_count,
25
26
  write_error_count: metric.write_error_count,
26
27
  gc_count: metric.gc_count,
@@ -50,14 +50,29 @@ class ProconBypassMan::Bypass::ProconToSwitch
50
50
  # 後続処理で入力値を取得できるように詰めておく
51
51
  ProconBypassMan::ProconDisplay::Status.instance.current = bypass_value.binary.to_procon_reader.to_hash
52
52
 
53
+ # NOTE: 外部からの入力を受け取る
54
+ external_input_data = nil
55
+ measurement.record_external_input_time do
56
+ # TODO: シリアルぽーとから読み取ると252.chrみたいなゴミデータを受け取ってEncoding::UndefinedConversionErrorが発生する可能性がある. 発生したら上限までretryした方がいいかも
57
+ if(data = ProconBypassMan::ExternalInput.read)
58
+ begin
59
+ external_input_data = ProconBypassMan::ExternalInput::ExternalData.parse!(data)
60
+ ProconBypassMan.logger.debug { "[ExternalInput] 読み取った値: { hex: #{external_input_data.hex}, raw_data: '#{external_input_data.raw_data}', buttons: #{external_input_data.buttons} }" }
61
+ rescue ProconBypassMan::ExternalInput::ParseError => e
62
+ ProconBypassMan.logger.error "[ExternalInput][#{e}] #{data.force_encoding('UTF-8').scrub}, #{data.force_encoding('ASCII-8BIT').codepoints} をparseできませんでした"
63
+ end
64
+ end
65
+ end
66
+
53
67
  result = measurement.record_write_time do
54
68
  begin
55
69
  ProconBypassMan::Retryable.retryable(tries: 5, on_no_retry: [Errno::EIO, Errno::ENODEV, Errno::EPROTO, IOError, Errno::ESHUTDOWN, Errno::ETIMEDOUT]) do
56
70
  begin
57
71
  # 終了処理を希望されているのでブロックを無視してメソッドを抜けてOK
58
72
  return(false) if will_terminate? # rubocop:disable Lint/NoReturnInBeginEndBlocks
73
+
59
74
  binary = ::ProconBypassMan::Procon::Rumbler.monitor do
60
- ProconBypassMan::Processor.new(bypass_value.binary).process
75
+ ProconBypassMan::Processor.new(bypass_value.binary).process(external_input_data: external_input_data)
61
76
  end
62
77
  self.gadget.write_nonblock(binary)
63
78
 
@@ -34,6 +34,7 @@ class ProconBypassMan::PrintBootMessageCommand
34
34
  ProconBypassMan::VERSION: #{@table[:pbm_version]}
35
35
  RUBY_VERSION: #{@table[:ruby_version]}
36
36
  Pbmenv::VERSION: #{@table[:pbmenv_version]}
37
+ PBM-Cloud Integration: #{ProconBypassMan::WebConnectivityChecker.new(ProconBypassMan.config.api_server)}
37
38
  pid: #{@table[:pid]}
38
39
  root: #{@table[:root_path]}
39
40
  pid_path: #{@table[:pid_path]}
@@ -56,6 +57,7 @@ class ProconBypassMan::PrintBootMessageCommand
56
57
  def self.execute
57
58
  message = BootMessage.new
58
59
  ProconBypassMan::ReportBootJob.perform_async(message.to_hash)
60
+ ProconBypassMan.logger.info message.to_s
59
61
  puts message.to_s
60
62
  end
61
63
  end
@@ -54,6 +54,7 @@ class ProconBypassMan::Configuration
54
54
 
55
55
  attr_accessor :enable_critical_error_logging
56
56
  attr_writer :verbose_bypass_log, :raw_setting, :never_exit_accidentally, :enable_home_led_on_connect
57
+ attr_writer :external_input_channels
57
58
  # 削除予定
58
59
  attr_writer :enable_reporting_pressed_buttons
59
60
 
@@ -193,4 +194,9 @@ class ProconBypassMan::Configuration
193
194
  true
194
195
  end
195
196
  end
197
+
198
+ # @return [Array<ProconBypassMan::ExternalInput::Channel::TCPIP, ProconBypassMan::ExternalInput::Channel::SerialPort>]
199
+ def external_input_channels
200
+ @external_input_channels || []
201
+ end
196
202
  end
@@ -0,0 +1,12 @@
1
+ module ProconBypassMan
2
+ module ExternalInput
3
+ module Channels
4
+ class Base
5
+ # @return [String, NilClass]
6
+ def read
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ module ProconBypassMan
2
+ module ExternalInput
3
+ module Channels
4
+ class SerialPortChannel < Base
5
+ attr_reader :serial_port
6
+
7
+ # @param [String] device_path
8
+ # @param [Integer] baud_rate
9
+ def initialize(device_path: , baud_rate: 9600)
10
+ require 'serialport'
11
+
12
+ super()
13
+ # data_bitsあたりは必要があれば設定ができるようにしたいがよくわからないのでとりあえずnilを入れる
14
+ data_bits = 8
15
+ stop_bits = 1
16
+ parity = SerialPort::NONE
17
+ @serial_port= SerialPort.new(device_path, baud_rate, data_bits, stop_bits, parity)
18
+ end
19
+
20
+ # @return [String, NilClass]
21
+ # NOTE: この実装では、::IO::EAGAINWaitReadableを実質的な終端として扱っているので、高速に書き込みがされると取りこぼす可能性がある.
22
+ # NOTE: read_nonblockでバッファから読み出すとき、バッファが空になるまでは::IO::EAGAINWaitReadableが起きない前提で実装している.
23
+ # NOTE: 取りこぼししないよう精度を上げるには、終端文字が来るまで1文字ずつ読む必要があるがパフォーマンスが犠牲になってしまう. この対策をするには、bypass処理の開始で非同期に1文字ずつ読み込むことをすると多少マシになるはず
24
+ def read
25
+ buffer = ''
26
+ loop do
27
+ begin
28
+ buffer += @serial_port.read_nonblock(1024) || ''
29
+ rescue ::IO::EAGAINWaitReadable
30
+ break
31
+ end
32
+ end
33
+
34
+ return nil if buffer.empty?
35
+
36
+ # NOTE: 高速に書き込まれた場合、複数のチャンクを含む可能性があるので、最初だけを切り取る
37
+ chunks = buffer.split("\n")
38
+ if(chunks.size > 1)
39
+ ProconBypassMan::SendErrorCommand.execute(
40
+ error: "[ExternalInput] シリアルポートから読み込んだchunkが複数あります. 高い書き込み頻度に耐えられていないので実装を見直してください。 (chunks.size: #{chunks.size})"
41
+ )
42
+ end
43
+ chunks.first
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,16 @@
1
+ # TODO: 実装する
2
+ # module ProconBypassMan
3
+ # module ExternalInput
4
+ # module Channels
5
+ # class TCPIP < Base
6
+ # def initialize(port: )
7
+ # super()
8
+ # # TODO: ここでTCPサーバを起動する(port)
9
+ # end
10
+ #
11
+ # def read
12
+ # end
13
+ # end
14
+ # end
15
+ # end
16
+ # end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProconBypassMan
4
+ module ExternalInput
5
+ module Channels
6
+ end
7
+ end
8
+ end
9
+
10
+ require "procon_bypass_man/external_input/channels/base"
11
+ require "procon_bypass_man/external_input/channels/serial_port_channel"
12
+ require "procon_bypass_man/external_input/channels/tcpip"
@@ -0,0 +1,79 @@
1
+ module ProconBypassMan
2
+ module ExternalInput
3
+ class ExternalData
4
+ UNPRESS_BUTTONS = Set.new(ProconBypassMan::Procon::ButtonCollection.available.map { |x| "un#{x}".to_sym })
5
+
6
+ # @return [String, NilClass] 16進数表現のデータ
7
+ attr_reader :hex
8
+
9
+ # @return [String, NilClass] ログに表示する用
10
+ attr_reader :raw_data
11
+
12
+ # @raise [ParseError]
13
+ # @return [ExternalData] JSON か カンマ区切りのbuttons
14
+ def self.parse!(raw_data)
15
+ raise ParseError unless raw_data.ascii_only?
16
+
17
+ if is_json(raw_data)
18
+ begin
19
+ json = JSON.parse(raw_data)
20
+ return new(hex: json['hex'], buttons: json['buttons'])
21
+ rescue JSON::ParserError
22
+ raise ParseError
23
+ end
24
+ end
25
+
26
+ return new(hex: nil, buttons: raw_data.scan(/:\w+:/).map { |x| x.gsub(':', '') }, raw_data: raw_data)
27
+ end
28
+
29
+ # @param [String] raw_data
30
+ # @return [Boolean]
31
+ def self.is_json(raw_data)
32
+ raw_data.start_with?('{')
33
+ end
34
+
35
+ # @param [String, NilClass] hex
36
+ # @param [Array<String>, NilClass] buttons
37
+ # @param [String, NilClass] raw_data
38
+ def initialize(hex: , buttons: , raw_data: nil)
39
+ @hex = hex
40
+ @buttons = buttons || []
41
+ @raw_data = raw_data
42
+ end
43
+
44
+ # @return [String, NilClass]
45
+ def to_binary
46
+ return nil if @hex.nil?
47
+ [@hex].pack('H*')
48
+ end
49
+
50
+ # @return [Array<Symbol>]
51
+ def press_buttons
52
+ buttons.select do |button|
53
+ ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[button]
54
+ end
55
+ end
56
+
57
+ # @return [Array<Symbol>]
58
+ def unpress_buttons
59
+ buttons.select { |button|
60
+ UNPRESS_BUTTONS.include?(button)
61
+ }.map { |b| to_button(b.to_s).to_sym }
62
+ end
63
+
64
+ # NOTE: ログに表示する用
65
+ # @return [Array<Symbol>]
66
+ def buttons
67
+ @buttons.map(&:to_sym)
68
+ end
69
+
70
+ private
71
+
72
+ # @return [String]
73
+ # NOTE: un#{button} って名前をbuttonに変換する
74
+ def to_button(button)
75
+ button.sub(/^un/, '')
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProconBypassMan
4
+ module ExternalInput
5
+ class ParseError < StandardError; end
6
+
7
+ @@channels = nil
8
+
9
+ # @return [Array<ProconBypassMan::ExternalInput::Channels::Base>]
10
+ def self.channels
11
+ @@channels
12
+ end
13
+
14
+ # @return [void]
15
+ def self.prepare_channels
16
+ @@channels = ProconBypassMan.config.external_input_channels
17
+ end
18
+
19
+ # @return [NilClass, String]
20
+ # NOTE: 外部入力からのreadがボトルネックになるなら、Concurrent::Futureを使ってプロコンからの読み出しと並列化することを検討する
21
+ def self.read
22
+ raise '外部入力が未初期化です' if @@channels.nil?
23
+
24
+ value = nil
25
+ @@channels.each do |channel|
26
+ value = channel.read
27
+ break if value
28
+ end
29
+ value
30
+ end
31
+ end
32
+ end
33
+
34
+ require "procon_bypass_man/external_input/external_data"
35
+ require "procon_bypass_man/external_input/channels.rb"
@@ -5,12 +5,13 @@ class ProconBypassMan::Processor
5
5
  @binary = binary
6
6
  end
7
7
 
8
+ # @param [ProconBypassMan::ExternalInput::ExternalData, NilClass] external_input_data
8
9
  # @return [String] 加工後の入力データ
9
- def process
10
+ def process(external_input_data: nil)
10
11
  return @binary.raw unless @binary.user_operation_data?
11
12
 
12
13
  procon = ProconBypassMan::Procon.new(@binary.raw)
13
14
  procon.apply!
14
- procon.to_binary
15
+ procon.to_binary(external_input_data: external_input_data)
15
16
  end
16
17
  end
@@ -37,4 +37,9 @@ class ProconBypassMan::Procon::ButtonCollection
37
37
  BUTTONS = ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP.keys.freeze
38
38
 
39
39
  LEFT_ANALOG_STICK = { byte_position: 6..8 }
40
+
41
+ # @return [Array<Symbol>]
42
+ def self.available
43
+ BUTTONS
44
+ end
40
45
  end
@@ -9,6 +9,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
9
9
  :time_taken_p95,
10
10
  :time_taken_p99,
11
11
  :time_taken_max,
12
+ :external_input_time_max,
12
13
  :read_error_count,
13
14
  :write_error_count,
14
15
  :gc_count,
@@ -26,6 +27,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
26
27
  write_time_max = 0
27
28
  read_time_max = 0
28
29
  time_taken_max = 0
30
+ external_input_time_max = 0
29
31
  interval_from_previous_succeed_max = 0
30
32
  @spans.each do |span|
31
33
  # NOTE @spans.map(&:write_time).sort.last と同じことだけど、処理コストを軽くするためにループを共通化する
@@ -33,6 +35,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
33
35
  read_time_max = span.read_time if write_time_max < span.read_time
34
36
  time_taken_max = span.time_taken if span.succeed && time_taken_max < span.time_taken
35
37
  interval_from_previous_succeed_max = span.interval_from_previous_succeed if span.succeed && interval_from_previous_succeed_max < span.interval_from_previous_succeed
38
+ external_input_time_max = span.external_input_time if span.succeed && external_input_time_max < span.external_input_time
36
39
  end
37
40
 
38
41
  # NOTE 今はGCを無効にしており、集計するまでもないのでコメントアウトにする. 今後GCを有効にしたバイパスをするかもしれないので残しておく
@@ -79,6 +82,7 @@ class ProconBypassMan::Procon::PerformanceMeasurement::MeasurementsSummarizer
79
82
  time_taken_p95,
80
83
  time_taken_p99,
81
84
  time_taken_max,
85
+ external_input_time_max,
82
86
  total_read_error_count,
83
87
  total_write_error_count,
84
88
  gc_count,
@@ -12,7 +12,7 @@ require 'procon_bypass_man/procon/performance_measurement/last_bypass_at'
12
12
  module ProconBypassMan::Procon::PerformanceMeasurement
13
13
  class PerformanceSpan
14
14
  attr_accessor :time_taken, :succeed, :interval_from_previous_succeed, :gc_count, :gc_time
15
- attr_reader :write_error_count, :read_error_count, :write_time, :read_time
15
+ attr_reader :write_error_count, :read_error_count, :write_time, :read_time, :external_input_time
16
16
 
17
17
  def initialize
18
18
  @write_error_count = 0
@@ -40,9 +40,15 @@ module ProconBypassMan::Procon::PerformanceMeasurement
40
40
  return result
41
41
  end
42
42
 
43
+ # @return [void]
43
44
  def record_read_time(&block)
44
45
  @read_time = Benchmark.realtime { block.call }
45
46
  end
47
+
48
+ # @return [void]
49
+ def record_external_input_time(&block)
50
+ @external_input_time = Benchmark.realtime { block.call }
51
+ end
46
52
  end
47
53
 
48
54
  # 全部送ると負荷になるので適当にまびく
@@ -128,7 +128,7 @@ class ProconBypassMan::Procon
128
128
  end
129
129
 
130
130
  # remote macro or pbm action
131
- if task = ProconBypassMan::RemoteAction::TaskQueueInProcess.non_blocking_shift
131
+ if(task = ProconBypassMan::RemoteAction::TaskQueueInProcess.non_blocking_shift)
132
132
  case task.type
133
133
  when ProconBypassMan::RemoteAction::Task::TYPE_MACRO
134
134
  no_op_step = :wait_for_0_3 # マクロの最後に固まって最後の入力をし続けるので、無の状態を最後に注入する
@@ -187,12 +187,27 @@ class ProconBypassMan::Procon
187
187
  status
188
188
  end
189
189
 
190
+ # @param [ProconBypassMan::ExternalInput::ExternalData, NilClass] external_input_data
190
191
  # @return [String]
191
- def to_binary
192
+ def to_binary(external_input_data: nil)
192
193
  if ongoing_mode.name != :manual
193
194
  return user_operation.binary.raw
194
195
  end
195
196
 
197
+ if external_input_data
198
+ if(external_input_data_raw_binary = external_input_data.to_binary)
199
+ self.user_operation.merge(external_input_data_raw_binary)
200
+ else
201
+ external_input_data.press_buttons.each do |button|
202
+ self.user_operation.press_button(button)
203
+ end
204
+ external_input_data.unpress_buttons.each do |button|
205
+ self.user_operation.unpress_button(button)
206
+ end
207
+ end
208
+ return self.user_operation.binary.raw
209
+ end
210
+
196
211
  if ongoing_macro.ongoing? && (step = ongoing_macro.next_step)
197
212
  BlueGreenProcess::SharedVariable.extend_run_on_this_process = true
198
213
  ongoing_macro.force_neutral_buttons&.each do |force_neutral_button|
@@ -1,5 +1,3 @@
1
- require 'singleton'
2
-
3
1
  class ProconBypassMan::ProconDisplay::Status
4
2
  include Singleton
5
3
 
@@ -1,14 +1,16 @@
1
1
  module ProconBypassMan
2
2
  class Retryable
3
- def self.retryable(tries: , retried: 0, on_no_retry: [])
3
+ def self.retryable(tries: , retried: 0, on_no_retry: [], log_label: nil)
4
4
  return yield(retried)
5
5
  rescue *on_no_retry
6
6
  raise
7
- rescue
7
+ rescue => e
8
8
  if tries <= retried
9
9
  raise
10
10
  else
11
11
  retried = retried + 1
12
+ ProconBypassMan.logger.debug "[Retryable]#{log_label && "[#{log_label}]"} #{e}が起きました。retryします。#{retried} / #{tries}"
13
+
12
14
  retry
13
15
  end
14
16
  end
@@ -0,0 +1,39 @@
1
+ class ProconBypassMan::WebConnectivityChecker
2
+ # @param [String, NilClass] url
3
+ def initialize(url)
4
+ @url = url
5
+ end
6
+
7
+ # @return [String]
8
+ def to_s
9
+ if @url.nil?
10
+ return "DISABLE"
11
+ end
12
+
13
+ if alive?
14
+ return "ENABLE (#{@url})"
15
+ else
16
+ return "UNREACHABLE (#{@url})"
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ # @return [Boolean]
23
+ def alive?
24
+ uri = URI.parse(@url)
25
+ response = nil
26
+
27
+ begin
28
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
29
+ request = Net::HTTP::Head.new(uri)
30
+ response = http.request(request)
31
+ end
32
+ rescue StandardError => e
33
+ ProconBypassMan.logger.error e
34
+ return false
35
+ end
36
+
37
+ response.is_a?(Net::HTTPSuccess) or response.is_a?(Net::HTTPMovedPermanently)
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProconBypassMan
4
- VERSION = "0.3.6"
4
+ VERSION = "0.3.7"
5
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'singleton'
3
4
  require "logger"
4
5
  require 'yaml'
5
6
  require "json"
@@ -40,6 +41,7 @@ require_relative "procon_bypass_man/support/cycle_sleep"
40
41
  require_relative "procon_bypass_man/support/can_over_process"
41
42
  require_relative "procon_bypass_man/support/retryable"
42
43
  require_relative "procon_bypass_man/support/renice_command"
44
+ require_relative "procon_bypass_man/support/web_connectivity_checker"
43
45
  require_relative "procon_bypass_man/procon_display"
44
46
  require_relative "procon_bypass_man/background"
45
47
  require_relative "procon_bypass_man/commands"
@@ -61,7 +63,7 @@ require_relative "procon_bypass_man/worker"
61
63
  require_relative "procon_bypass_man/websocket/client"
62
64
  require_relative "procon_bypass_man/websocket/watchdog"
63
65
  require_relative "procon_bypass_man/websocket/forever"
64
-
66
+ require_relative "procon_bypass_man/external_input"
65
67
  require_relative "procon_bypass_man/remote_action"
66
68
 
67
69
  STDOUT.sync = true
@@ -122,6 +124,8 @@ module ProconBypassMan
122
124
  return
123
125
  end
124
126
 
127
+ ProconBypassMan::ExternalInput.prepare_channels
128
+
125
129
  ready_pbm
126
130
  Runner.new(gadget: gadget, procon: procon).run # ここでblockingする
127
131
  terminate_pbm
@@ -130,6 +134,8 @@ module ProconBypassMan
130
134
  # 実行ファイル(app.rb)から呼び出している
131
135
  # @return [void]
132
136
  def self.configure(&block)
137
+ require_relative "procon_bypass_man/external_input"
138
+
133
139
  @@configuration = ProconBypassMan::Configuration.new
134
140
  @@configuration.instance_eval(&block)
135
141
  nil
@@ -12,7 +12,11 @@ begin
12
12
  gemfile do
13
13
  source 'https://rubygems.org'
14
14
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
15
- gem 'procon_bypass_man', '0.3.6'
15
+ gem 'procon_bypass_man', '0.3.7'
16
+ # uncomment if you want to use master branch
17
+ # gem 'procon_bypass_man', github: 'splaplapla/procon_bypass_man', branch: 'master'
18
+ # uncomment if you want to use serial communication feature
19
+ # gem "serialport"
16
20
  end
17
21
  rescue Bundler::Source::Git::GitCommandError => e
18
22
  retry_count_on_git_command_error = retry_count_on_git_command_error + 1
@@ -49,6 +53,11 @@ ProconBypassMan.configure do |config|
49
53
 
50
54
  # 接続に成功したらコントローラーのHOME LEDを光らせるか
51
55
  config.enable_home_led_on_connect = true
56
+
57
+ # シリアル通信やTCP/IP経由で入力するときに設定してください
58
+ # config.external_input_channels = [
59
+ # ProconBypassMan::ExternalInput::Channels::SerialPortChannel.new(device_path: '/dev/serial0', baud_rate: 9600),
60
+ # ]
52
61
  end
53
62
 
54
63
  ProconBypassMan.run(setting_path: "/usr/share/pbm/current/setting.yml")
@@ -12,7 +12,11 @@ begin
12
12
  gemfile do
13
13
  source 'https://rubygems.org'
14
14
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
15
- gem 'procon_bypass_man', '0.3.6'
15
+ gem 'procon_bypass_man', '0.3.7'
16
+ # uncomment if you want to use master branch
17
+ # gem 'procon_bypass_man', github: 'splaplapla/procon_bypass_man', branch: 'master'
18
+ # uncomment if you want to use serial communication feature
19
+ # gem "serialport"
16
20
  end
17
21
  rescue Bundler::Source::Git::GitCommandError => e
18
22
  retry_count_on_git_command_error = retry_count_on_git_command_error + 1
@@ -54,6 +58,12 @@ ProconBypassMan.configure do |config|
54
58
 
55
59
  # 接続に成功したらコントローラーのHOME LEDを光らせるか
56
60
  config.enable_home_led_on_connect = true
61
+
62
+ # シリアル通信やTCP/IP経由で入力するときに設定してください
63
+ # config.external_input_channels = [
64
+ # ProconBypassMan::ExternalInput::Channels::SerialPortChannel.new(device_path: '/dev/serial0', baud_rate: 9600),
65
+ <%-# ProconBypassMan::ExternalInput::Channels::TCPIP.new(port: 9000), # TODO: まだ実装していない -%>
66
+ # ]
57
67
  end
58
68
 
59
69
  ProconBypassMan.run(setting_path: "/usr/share/pbm/current/setting.yml")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procon_bypass_man
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - jiikko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-12 00:00:00.000000000 Z
11
+ date: 2023-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pbmenv
@@ -95,9 +95,12 @@ files:
95
95
  - bin/generate_default_app
96
96
  - bin/generate_output_report
97
97
  - bin/setup
98
+ - bin/validate_external_input
98
99
  - docs/getting_started.md
99
100
  - docs/how_to_connect_procon.md
100
101
  - docs/run_pbm_on_my_branch.md
102
+ - docs/setting/integration_external_input_serial_port.md
103
+ - docs/setting/integration_external_input_serial_port_format.md
101
104
  - docs/setting/left-analogstick-cap.md
102
105
  - docs/setting/splatoon2_macro_dasei_cancel.md
103
106
  - docs/setting/splatoon2_macro_forward_ikarole.md
@@ -176,6 +179,12 @@ files:
176
179
  - lib/procon_bypass_man/device_model.rb
177
180
  - lib/procon_bypass_man/device_status.rb
178
181
  - lib/procon_bypass_man/ephemeral_configuration.rb
182
+ - lib/procon_bypass_man/external_input.rb
183
+ - lib/procon_bypass_man/external_input/channels.rb
184
+ - lib/procon_bypass_man/external_input/channels/base.rb
185
+ - lib/procon_bypass_man/external_input/channels/serial_port_channel.rb
186
+ - lib/procon_bypass_man/external_input/channels/tcpip.rb
187
+ - lib/procon_bypass_man/external_input/external_data.rb
179
188
  - lib/procon_bypass_man/plugin/splatoon2/macro/charge_tansan_bomb.rb
180
189
  - lib/procon_bypass_man/plugin/splatoon2/macro/dasei_cancel.rb
181
190
  - lib/procon_bypass_man/plugin/splatoon2/macro/fast_return.rb
@@ -281,6 +290,7 @@ files:
281
290
  - lib/procon_bypass_man/support/update_remote_pbm_job_status_http_client.rb
282
291
  - lib/procon_bypass_man/support/uptime.rb
283
292
  - lib/procon_bypass_man/support/usb_device_controller.rb
293
+ - lib/procon_bypass_man/support/web_connectivity_checker.rb
284
294
  - lib/procon_bypass_man/support/yaml_loader.rb
285
295
  - lib/procon_bypass_man/support/yaml_writer.rb
286
296
  - lib/procon_bypass_man/version.rb