procon_bypass_man 0.3.7 → 0.3.8

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/gitleacks.yml +1 -1
  3. data/.github/workflows/release.yml +1 -1
  4. data/.github/workflows/ruby.yml +2 -2
  5. data/CHANGELOG.md +6 -0
  6. data/Gemfile.lock +2 -2
  7. data/README.md +2 -2
  8. data/bin/console +0 -3
  9. data/docs/getting_started.md +25 -2
  10. data/lib/procon_bypass_man/commands/print_boot_message_command.rb +3 -2
  11. data/lib/procon_bypass_man/configuration.rb +25 -14
  12. data/lib/procon_bypass_man/external_input/boot_message.rb +21 -0
  13. data/lib/procon_bypass_man/external_input/channels/base.rb +10 -0
  14. data/lib/procon_bypass_man/external_input/channels/serial_port_channel.rb +9 -1
  15. data/lib/procon_bypass_man/external_input/channels/tcpip_channel.rb +131 -0
  16. data/lib/procon_bypass_man/external_input/channels.rb +1 -1
  17. data/lib/procon_bypass_man/external_input.rb +5 -9
  18. data/lib/procon_bypass_man/support/forever.rb +51 -0
  19. data/lib/procon_bypass_man/support/proccess_cheacker.rb +14 -0
  20. data/lib/procon_bypass_man/support/retryable.rb +2 -1
  21. data/lib/procon_bypass_man/support/simple_tcp_server.rb +63 -0
  22. data/lib/procon_bypass_man/support/watchdog.rb +23 -0
  23. data/lib/procon_bypass_man/support/web_connectivity_checker.rb +4 -2
  24. data/lib/procon_bypass_man/version.rb +1 -1
  25. data/lib/procon_bypass_man/websocket/client.rb +11 -9
  26. data/lib/procon_bypass_man.rb +11 -4
  27. data/project_template/app.rb +2 -1
  28. data/project_template/app.rb.erb +2 -2
  29. data/sig/main.rbs +2 -2
  30. metadata +8 -5
  31. data/lib/procon_bypass_man/external_input/channels/tcpip.rb +0 -16
  32. data/lib/procon_bypass_man/websocket/forever.rb +0 -47
  33. data/lib/procon_bypass_man/websocket/watchdog.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86e705d2907d51077191585d2cd16fb198a8a55226613d25f44b15b5e58f8806
4
- data.tar.gz: a13290f301295913bbc30a06a1e06689dc5aae328cadd39ed1e91982ff7586d2
3
+ metadata.gz: 1933db5fe8c39267f980712a5477a457277cdd3ce9148a81966c1e1d818805e8
4
+ data.tar.gz: 404ce5c7ada74a92afb5abf2b2f583c3fe1c87bbc4d3ae2372e0ff3f961d9245
5
5
  SHA512:
6
- metadata.gz: 30618560c54d7c7a730bf97d74b601fce287d6900ed3eaf3c54b1071349b8a7d8b9e346c9099f6dbfad25a568710c5aaa4ce09ad4b12805a2f6d2d3a8134b27f
7
- data.tar.gz: 96c796ecbbfa544a0427537a50ea17a0b7f10eee360bc6b072872c7621627dccf5403e78bdc2b580e5957fc5758f9b58979ef1f7f341da1a58b849a693ae79f8
6
+ metadata.gz: eeeb32ff42fc02d2b40b19be2a1eba55a9fc5ffc6a3f08af47b4ed86bb67edba60dc7ef409f892ba9ef21953a349abda01a28d04f4a4f99f0032382a68c2f904
7
+ data.tar.gz: 811f537dd6ea2659678911678022c02734a2a21ef099d7ed140067a56e88edf0fabc4aec8264c052e8e8e9759c54085a17ab7a72c668a338560250b880395e2f
@@ -7,6 +7,6 @@ jobs:
7
7
  runs-on: ubuntu-latest
8
8
  timeout-minutes: 5
9
9
  steps:
10
- - uses: actions/checkout@v1
10
+ - uses: actions/checkout@v3
11
11
  - name: gitleaks-action
12
12
  uses: zricethezav/gitleaks-action@v1.6.0
@@ -11,7 +11,7 @@ jobs:
11
11
  contents: write
12
12
 
13
13
  steps:
14
- - uses: actions/checkout@v2
14
+ - uses: actions/checkout@v3
15
15
  with:
16
16
  token: ${{ secrets.GITHUB_TOKEN }}
17
17
  - name: Set up Ruby 3.0.1
@@ -16,10 +16,10 @@ jobs:
16
16
  timeout-minutes: 5
17
17
  strategy:
18
18
  matrix:
19
- ruby-version: ['3.0.1', '3.1.1']
19
+ ruby-version: ['3.0', '3.1', '3.2']
20
20
 
21
21
  steps:
22
- - uses: actions/checkout@v2
22
+ - uses: actions/checkout@v3
23
23
  - name: Set up Ruby
24
24
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
25
25
  # change this to (see https://github.com/ruby/setup-ruby#versioning):
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.3.8] 2023-05-03
2
+ * TCP/IP経由でPBMに入力ができるようになりました
3
+ * 多重起動ができないようにしました
4
+ * pbm-cloudとの連携時に、pbmenvを使っているかの判定を修正しました
5
+ * actioncable(websocket)serverのURLをpbm-cloudから取得するようにしました
6
+
1
7
  ## [0.3.7] 2023-04-06
2
8
  * PBM-Cloudと連携しているかをbootメッセージに表示ようになりました
3
9
  * シリアルポート経由でPBMに入力ができるようになりました
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- procon_bypass_man (0.3.7)
4
+ procon_bypass_man (0.3.8)
5
5
  action_cable_client
6
6
  blue_green_process (= 0.1.4.2)
7
7
  pbmenv (>= 0.1.11)
@@ -38,7 +38,7 @@ GEM
38
38
  parallel (1.21.0)
39
39
  parser (3.0.3.2)
40
40
  ast (~> 2.4.1)
41
- pbmenv (0.1.11)
41
+ pbmenv (0.1.12)
42
42
  pry (0.14.1)
43
43
  coderay (~> 1.1)
44
44
  method_source (~> 1.0)
data/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  https://user-images.githubusercontent.com/1664497/171327108-f12f56a5-fc36-48da-95a5-65e976553a20.mov
13
13
 
14
- ## 使うハードウェア
14
+ ## 必要なハードウェア
15
15
  * Nintendo Switch Proコントローラー
16
16
  * Switch本体とドック
17
17
  * Raspberry Pi4 (Raspberry Pi OS)
@@ -38,7 +38,7 @@ https://user-images.githubusercontent.com/1664497/171327108-f12f56a5-fc36-48da-9
38
38
  * レイヤーを切り替える方法は?
39
39
  * 設定ファイルに記述している `prefix_keys_for_changing_layer`の後ろにあるキーを同時押しながら、十字キーのどれかを押すことで任意のレイヤーに切り替わります
40
40
  * このツールでできることは?
41
- * キーリマップ, 連射, マクロ
41
+ * キーリマップ, 連射, マクロ, 外部ツールからの入力
42
42
  * リマップは1つのキーを別のキーに割り当てます
43
43
  * 連射中には特定のキーの入力を無視したり、複数のキーをトリガーに連射することができます
44
44
  * どうしてsudoが必要なの?
data/bin/console CHANGED
@@ -14,9 +14,6 @@ require "pry"
14
14
 
15
15
  require "irb"
16
16
 
17
- # TODO: replのときチェックが入らないので、書く詳細な実装に触れないようにする。PBM_ENVを参照するなどをする
18
- ProconBypassMan::ExternalInput.prepare_channels
19
-
20
17
  ProconBypassMan.config.logger = Logger.new($stdout)
21
18
 
22
19
  IRB.start(__FILE__)
@@ -18,7 +18,8 @@
18
18
  * [設定ファイルの書き方がわからない、エラーが起きるとき](#設定ファイルの書き方がわからない、エラーが起きるとき)
19
19
  * [procon_bypass_manのアップグレード方法](#procon_bypass_manのアップグレード方法)
20
20
  * [procon_bypass_man_cloudについて](#procon_bypass_man_cloudについて)
21
- * [シリアルポート・GPIOから読み取る](#シリアルポートから読み取る)
21
+ * [シリアルポート連携](#シリアルポート連携)
22
+ * [TCPIP連携](#TCPIP連携)
22
23
  * [最適化について](#最適化について)
23
24
 
24
25
  ## はじめに
@@ -252,10 +253,32 @@ procon_bypass_man_cloudとの接続が完了後、Raspberry Piを起動時にpro
252
253
 
253
254
  セットアップ方法は https://pbm-cloud.jiikko.com/faq に書いています。
254
255
 
255
- ## シリアルポートから読み取る
256
+ ## シリアルポート連携
256
257
  * [ラズベリーパイのシリアルポート(GPIO)へ書き込んでPBM経由してSwitchへ入力をする方法](/docs/setting/integration_external_input_serial_port.md)
257
258
  * [ラズベリーパイのシリアルポート(GPIO)に書き込むフォーマットについて](/docs/setting/integration_external_input_serial_port_format.md)
258
259
 
260
+ ## TCPIP連携
261
+ procon_bypass_man: 0.3.8 からTCP/IP経由で入力ができるようになりました。
262
+ 書き込みフォーマットはJSONのみに対応しています。JSONの詳細な仕様については [ラズベリーパイのシリアルポート(GPIO)に書き込むフォーマットについて](/docs/setting/integration_external_input_serial_port_format.md) を参照してください。
263
+
264
+ 次は、PBM側の設定方法についてです。app.rbに以下の行を追加すると、連携するためのTCPサーバを起動するようになります。
265
+
266
+ ```diff
267
+ + config.external_input_channels = [
268
+ + ProconBypassMan::ExternalInput::Channels::TCPIPChannel.new(port: 9000),
269
+ + ]
270
+ ```
271
+
272
+ クライアント側のサンプル実装は次の通りです。これを実行するとAボタンを入力します。
273
+
274
+ ```ruby
275
+ socket = TCPSocket.new('ras1.local', 9000)
276
+ json = { buttons: [:a] }.to_json
277
+ message = "#{json}\r\n"
278
+ socket.write(message)
279
+ puts socket.gets
280
+ ```
281
+
259
282
  ## 最適化について
260
283
  本稿では、Rubyの最適化について書きます。上級者向けです。適用しなくても普通に動きますが、逆に適用したことで何らかのケースで遅くなる場合があるかもしれません。
261
284
 
@@ -10,7 +10,7 @@ class ProconBypassMan::PrintBootMessageCommand
10
10
  @table[:pid_path] = ProconBypassMan.pid_path
11
11
  @table[:setting_path] = ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path
12
12
  @table[:uptime_from_boot] = ProconBypassMan::Uptime.from_boot
13
- @table[:use_pbmenv] = !(!!`which pbmenv`.empty?)
13
+ @table[:use_pbmenv] = ProconBypassMan.root.start_with?(Pbmenv::PBM_DIR)
14
14
  @table[:session_id] = ProconBypassMan.session_id
15
15
  @table[:device_id] = ProconBypassMan.device_id
16
16
  @table[:never_exit_accidentally] = ProconBypassMan.config.never_exit_accidentally
@@ -34,7 +34,8 @@ 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
+ PBM-Cloud Integration: #{ProconBypassMan::WebConnectivityChecker.new(ProconBypassMan.config.api_server, ProconBypassMan.config.current_ws_server_url)}
38
+ ExternalInput Integration: #{ProconBypassMan::ExternalInput::BootMessage.new(channels: ProconBypassMan::ExternalInput.channels)}
38
39
  pid: #{@table[:pid]}
39
40
  root: #{@table[:root_path]}
40
41
  pid_path: #{@table[:pid_path]}
@@ -19,9 +19,11 @@ class ProconBypassMan::Configuration
19
19
  @@pid_path ||= File.expand_path("#{root}/pbm_pid", __dir__).freeze
20
20
  end
21
21
 
22
- # @return [Integer]
22
+ # @return [Integer, nil]
23
23
  def pid
24
24
  File.read(pid_path).to_i
25
+ rescue Errno::ENOENT
26
+ nil
25
27
  end
26
28
 
27
29
  def digest_path
@@ -70,6 +72,7 @@ class ProconBypassMan::Configuration
70
72
  if defined?(@root)
71
73
  @root
72
74
  else
75
+ ProconBypassMan.logger.warn 'root pathが未設定です'
73
76
  File.expand_path('..', __dir__ || ".").freeze
74
77
  end
75
78
  end
@@ -126,22 +129,30 @@ class ProconBypassMan::Configuration
126
129
  end
127
130
 
128
131
  # @return [String, NilClass]
129
- def current_ws_server
130
- if (uri = URI.parse(api_server))
131
- if uri.port == 443
132
- return "ws://#{uri.host}"
132
+ def current_ws_server_url
133
+ return @current_ws_server_url if defined?(@current_ws_server_url)
134
+ return unless api_server
135
+
136
+ response_json = ProconBypassMan::HttpClient.new(server: api_server, path: '/api/v1/configuration').get
137
+ ws_server_url = response_json&.fetch("ws_server_url", nil)
138
+
139
+ begin
140
+ uri = URI.parse(ws_server_url)
141
+ if uri.scheme == 'ws'
142
+ @current_ws_server_url = uri.to_s
143
+ return @current_ws_server_url
144
+ elsif uri.scheme == 'wss' # NOTE: SSL_CTX_use_certificate: ee key too small (OpenSSL::SSL::SSLError) が起きるので
145
+ uri.scheme = 'ws'
146
+ @current_ws_server_url = uri.to_s
147
+ return @current_ws_server_url
133
148
  else
134
- return "ws://#{uri.host}:#{uri.port}"
149
+ ProconBypassMan.logger.warn { "#{ws_server_url} is invalid." }
150
+ return nil
135
151
  end
152
+ rescue URI::InvalidURIError => e
153
+ ProconBypassMan.logger.warn { "#{ws_server_url} is invalid. #{e}" }
154
+ nil
136
155
  end
137
- rescue URI::InvalidURIError
138
- nil
139
- end
140
-
141
- # @return [String, NilClass]
142
- def current_ws_server_url
143
- return unless current_ws_server
144
- "#{current_ws_server}/websocket/"
145
156
  end
146
157
 
147
158
  # @return [Boolean]
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ProconBypassMan
4
+ module ExternalInput
5
+ class BootMessage
6
+ # @return [ProconBypassMan::ExternalInput::Channels::Base]
7
+ def initialize(channels: )
8
+ @channels = channels
9
+ end
10
+
11
+ # @return [String]
12
+ def to_s
13
+ if @channels.nil? or @channels.empty?
14
+ return 'DISABLE'
15
+ end
16
+
17
+ @channels.map(&:display_name_for_boot_message).join(', ')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -6,6 +6,16 @@ module ProconBypassMan
6
6
  def read
7
7
  raise NotImplementedError
8
8
  end
9
+
10
+ # @return [void]
11
+ def shutdown
12
+ raise NotImplementedError
13
+ end
14
+
15
+ # @return [String]
16
+ def display_name_for_boot_message
17
+ raise NotImplementedError
18
+ end
9
19
  end
10
20
  end
11
21
  end
@@ -14,7 +14,7 @@ module ProconBypassMan
14
14
  data_bits = 8
15
15
  stop_bits = 1
16
16
  parity = SerialPort::NONE
17
- @serial_port= SerialPort.new(device_path, baud_rate, data_bits, stop_bits, parity)
17
+ @serial_port = SerialPort.new(device_path, baud_rate, data_bits, stop_bits, parity)
18
18
  end
19
19
 
20
20
  # @return [String, NilClass]
@@ -42,6 +42,14 @@ module ProconBypassMan
42
42
  end
43
43
  chunks.first
44
44
  end
45
+
46
+ def shutdown
47
+ # no-op
48
+ end
49
+
50
+ def display_name_for_boot_message
51
+ 'SerialPort'
52
+ end
45
53
  end
46
54
  end
47
55
  end
@@ -0,0 +1,131 @@
1
+ module ProconBypassMan
2
+ module ExternalInput
3
+ module Channels
4
+ class TCPIPChannel < Base
5
+ class ShutdownSignal < StandardError; end
6
+
7
+ class AppServer < SimpleTCPServer
8
+ @command_queue = Queue.new
9
+
10
+ class << self
11
+ attr_accessor :command_queue
12
+ end
13
+
14
+ def post_init
15
+ ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] A client has connected" }
16
+ end
17
+
18
+ def unbind
19
+ ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] A client has disconnected" }
20
+ end
21
+
22
+ # @return [String]
23
+ def receive_data(client, data)
24
+ case data
25
+ when /^{/
26
+ self.class.command_queue.push(data)
27
+ client.write("OK\n")
28
+ return
29
+ when /^\n/
30
+ if self.class.command_queue.empty?
31
+ client.write("EMPTY\n")
32
+ return
33
+ end
34
+
35
+ data = self.class.command_queue.pop
36
+ client.write("#{data}\n")
37
+ return
38
+ else
39
+ client.write("Unknown command\n")
40
+ return
41
+ end
42
+ end
43
+ end
44
+
45
+ def initialize(port: )
46
+ @port = port
47
+ super()
48
+
49
+ begin
50
+ @server = AppServer.new('0.0.0.0', @port)
51
+ rescue Errno::EADDRINUSE # NOTE: Address already in use - bind(2) for "0.0.0.0" port XXXX
52
+ ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] 起動に失敗しました。#{e.message}")
53
+ @server_thread = Thread.start {}
54
+ return
55
+ end
56
+
57
+ # NOTE: masterプロセスで起動する
58
+ @server_thread = Thread.start do
59
+ begin
60
+ loop do
61
+ @server.start_server
62
+ @server.run
63
+ end
64
+ rescue Errno::EPIPE, EOFError, Errno::ECONNRESET => e
65
+ ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] #{e.message}(#{e})")
66
+ sleep(5)
67
+
68
+ @server.shutdown
69
+ retry
70
+ rescue ShutdownSignal => e
71
+ ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] ShutdownSignalを受け取りました。終了します。")
72
+ @server.shutdown
73
+ rescue => e
74
+ ProconBypassMan::SendErrorCommand.execute(error: "[ExternalInput][TCPIPChannel] #{e.message}(#{e})")
75
+ end
76
+ end
77
+ end
78
+
79
+ # NOTE: bypassプロセスから呼ばれ、masterプロセスへ繋ぐ
80
+ # @return [String, NilClass]
81
+ def read
82
+ @socket ||= TCPSocket.new('0.0.0.0', @port)
83
+ read_command = "\n"
84
+ @socket.write(read_command)
85
+ response = @socket.gets&.strip
86
+ # ProconBypassMan.logger.debug { "Received: #{response}" }
87
+
88
+ case response
89
+ when /^{/
90
+ return response
91
+ when /^EMPTY/, ''
92
+ return nil
93
+ else
94
+ ProconBypassMan.logger.warn { "[ExternalInput][TCPIPChannel] Unknown response(#{response}, codepoints: #{response.codepoints})" }
95
+ return nil
96
+ end
97
+ rescue Errno::EPIPE, EOFError => e
98
+ @socket = nil
99
+ sleep(10)
100
+ ProconBypassMan.logger.error { "[ExternalInput][TCPIPChannel] #{e.message}!!!!!!!(#{e})" }
101
+ retry
102
+ rescue => e
103
+ @socket = nil
104
+ ProconBypassMan.logger.error { "[ExternalInput][TCPIPChannel] #{e.message} が起きました(#{e})" }
105
+ return nil
106
+ end
107
+
108
+ def shutdown
109
+ ProconBypassMan.logger.info { "[ExternalInput][TCPIPChannel] shutdown" }
110
+ @server_thread.raise(ShutdownSignal)
111
+ end
112
+
113
+ def alive_server?
114
+ return false if not @server_thread.alive?
115
+
116
+ begin
117
+ TCPSocket.new('0.0.0.0', @port).close
118
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET
119
+ return false
120
+ end
121
+
122
+ true
123
+ end
124
+
125
+ def display_name_for_boot_message
126
+ "TCPIP(port: #{@port})"
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -9,4 +9,4 @@ end
9
9
 
10
10
  require "procon_bypass_man/external_input/channels/base"
11
11
  require "procon_bypass_man/external_input/channels/serial_port_channel"
12
- require "procon_bypass_man/external_input/channels/tcpip"
12
+ require "procon_bypass_man/external_input/channels/tcpip_channel"
@@ -4,25 +4,20 @@ module ProconBypassMan
4
4
  module ExternalInput
5
5
  class ParseError < StandardError; end
6
6
 
7
- @@channels = nil
8
-
9
7
  # @return [Array<ProconBypassMan::ExternalInput::Channels::Base>]
10
8
  def self.channels
11
- @@channels
9
+ @@channels ||= ProconBypassMan.config.external_input_channels
12
10
  end
13
11
 
14
- # @return [void]
15
- def self.prepare_channels
16
- @@channels = ProconBypassMan.config.external_input_channels
12
+ def self.shutdown
13
+ channels.each(&:shutdown)
17
14
  end
18
15
 
19
16
  # @return [NilClass, String]
20
17
  # NOTE: 外部入力からのreadがボトルネックになるなら、Concurrent::Futureを使ってプロコンからの読み出しと並列化することを検討する
21
18
  def self.read
22
- raise '外部入力が未初期化です' if @@channels.nil?
23
-
24
19
  value = nil
25
- @@channels.each do |channel|
20
+ channels.each do |channel|
26
21
  value = channel.read
27
22
  break if value
28
23
  end
@@ -33,3 +28,4 @@ end
33
28
 
34
29
  require "procon_bypass_man/external_input/external_data"
35
30
  require "procon_bypass_man/external_input/channels.rb"
31
+ require "procon_bypass_man/external_input/boot_message"
@@ -0,0 +1,51 @@
1
+ module ProconBypassMan
2
+ class Forever
3
+ # 動作確認方法
4
+ # - 10秒ごとにrefreshするのでタイムアウトは起きない
5
+ # - ProconBypassMan::Forever.run { |watchdog| loop { puts(:hi); sleep(10); watchdog.active! } }
6
+ # - タイムアウトが起きること
7
+ # - ProconBypassMan::Forever.run { |watchdog| loop { puts(:hi); sleep(10); } }
8
+ def self.run(&block)
9
+ loop do
10
+ new.run(&block)
11
+ end
12
+ end
13
+
14
+ # @return [void]
15
+ def run(&block)
16
+ raise(ArgumentError, "need a block") unless block_given?
17
+
18
+ thread, watchdog = work_one(callable: block)
19
+ wait_and_kill_if_outdated(thread, watchdog)
20
+ end
21
+
22
+ # @param [Proc] callable
23
+ # @return [Array<Thread, ProconBypassMan::Watchdog>]
24
+ def work_one(callable: )
25
+ watchdog = ProconBypassMan::Watchdog.new
26
+ thread = Thread.start do
27
+ callable.call(watchdog)
28
+ rescue => e
29
+ ProconBypassMan.logger.error("[Forever] #{e.full_message}")
30
+ end
31
+
32
+ return [thread, watchdog]
33
+ end
34
+
35
+ # @param [ProconBypassMan::Watchdog] watchdog
36
+ # @param [Thread] thread
37
+ # @return [void]
38
+ def wait_and_kill_if_outdated(thread, watchdog)
39
+ loop do
40
+ if watchdog.outdated?
41
+ watchdog.active!
42
+ ProconBypassMan.logger.error("watchdog timeout!!")
43
+ thread.kill
44
+ return
45
+ end
46
+
47
+ sleep(10)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ class ProconBypassMan::ProcessChecker
2
+ # @param [integer] pid
3
+ # @return [Boolean]
4
+ def self.running?(pid)
5
+ begin
6
+ Process.kill(0, pid)
7
+ true
8
+ rescue Errno::ESRCH
9
+ false
10
+ rescue Errno::EPERM
11
+ true
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,6 @@
1
1
  module ProconBypassMan
2
2
  class Retryable
3
- def self.retryable(tries: , retried: 0, on_no_retry: [], log_label: nil)
3
+ def self.retryable(tries: , retried: 0, on_no_retry: [], log_label: nil, interval_on_retry: 0)
4
4
  return yield(retried)
5
5
  rescue *on_no_retry
6
6
  raise
@@ -11,6 +11,7 @@ module ProconBypassMan
11
11
  retried = retried + 1
12
12
  ProconBypassMan.logger.debug "[Retryable]#{log_label && "[#{log_label}]"} #{e}が起きました。retryします。#{retried} / #{tries}"
13
13
 
14
+ sleep(interval_on_retry)
14
15
  retry
15
16
  end
16
17
  end
@@ -0,0 +1,63 @@
1
+ require 'socket'
2
+
3
+ class SimpleTCPServer
4
+ def initialize(host, port)
5
+ @host = host
6
+ @port = port
7
+ end
8
+
9
+ def start_server
10
+ @connections = []
11
+ @server = TCPServer.new(@host, @port)
12
+ end
13
+
14
+ def run
15
+ loop do
16
+ timeout = 5 # 5秒のタイムアウト
17
+ readable, _ = IO.select(@connections + [@server], nil, nil, timeout)
18
+ next if readable.nil? # timeoutを迎えるとnilになる
19
+
20
+ readable.each do |socket|
21
+ if socket == @server
22
+ client = @server.accept
23
+ post_init
24
+ @connections << client
25
+ else
26
+ data = socket.gets
27
+ if data.nil?
28
+ @connections.delete(socket)
29
+ unbind
30
+ socket.close
31
+ else
32
+ receive_data(socket, data)
33
+ end
34
+ end
35
+ end
36
+ rescue Errno::EBADF, IOError => e
37
+ unbind
38
+ @connections = []
39
+ @server.close
40
+ end
41
+ end
42
+
43
+ def shutdown
44
+ @server.close
45
+ end
46
+
47
+ # @return [Integer]
48
+ def connections_size
49
+ @connections.size
50
+ end
51
+
52
+ def post_init
53
+ # Override this method
54
+ end
55
+
56
+ def receive_data(socket, data)
57
+ # Override this method
58
+ end
59
+
60
+ def unbind
61
+ # Override this method
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module ProconBypassMan
2
+ class Watchdog
3
+ def initialize(timeout: 100)
4
+ @timeout = timeout
5
+ active!
6
+ end
7
+
8
+ # @return [Boolean]
9
+ def outdated?
10
+ @time < Time.now
11
+ end
12
+
13
+ # @return [Time]
14
+ def time
15
+ @time
16
+ end
17
+
18
+ # @return [void]
19
+ def active!
20
+ @time = Time.now + @timeout
21
+ end
22
+ end
23
+ end
@@ -1,7 +1,9 @@
1
1
  class ProconBypassMan::WebConnectivityChecker
2
2
  # @param [String, NilClass] url
3
- def initialize(url)
3
+ # @param [String, NilClass] ws_url
4
+ def initialize(url, ws_url)
4
5
  @url = url
6
+ @ws_url = ws_url
5
7
  end
6
8
 
7
9
  # @return [String]
@@ -11,7 +13,7 @@ class ProconBypassMan::WebConnectivityChecker
11
13
  end
12
14
 
13
15
  if alive?
14
- return "ENABLE (#{@url})"
16
+ return "ENABLE (#{@url}, #{@ws_url})"
15
17
  else
16
18
  return "UNREACHABLE (#{@url})"
17
19
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProconBypassMan
4
- VERSION = "0.3.7"
4
+ VERSION = "0.3.8"
5
5
  end
@@ -7,11 +7,13 @@ module ProconBypassMan
7
7
  return unless ProconBypassMan.config.enable_ws?
8
8
 
9
9
  Thread.start do
10
- Forever.run { run }
10
+ ProconBypassMan::Forever.run do |watchdog|
11
+ run(watchdog: watchdog)
12
+ end
11
13
  end
12
14
  end
13
15
 
14
- def self.run
16
+ def self.run(watchdog: )
15
17
  EventMachine.run do
16
18
  client = ActionCableClient.new(
17
19
  ProconBypassMan.config.current_ws_server_url, {
@@ -20,15 +22,15 @@ module ProconBypassMan
20
22
  )
21
23
 
22
24
  client.connected {
23
- ProconBypassMan.logger.info('websocket client: successfully connected in ProconBypassMan::Websocket::Client')
25
+ ProconBypassMan.logger.info('[WebsocketClient] successfully connected in ProconBypassMan::Websocket::Client')
24
26
  }
25
27
  client.subscribed { |msg|
26
- ProconBypassMan.logger.info("websocket client: subscribed(#{msg})")
28
+ ProconBypassMan.logger.info("[WebsocketClient] subscribed(#{msg})")
27
29
  ProconBypassMan::SyncDeviceStatsJob.perform(ProconBypassMan::DeviceStatus.current)
28
30
  }
29
31
 
30
32
  client.received do |data|
31
- ProconBypassMan.logger.info('websocket client: received!!')
33
+ ProconBypassMan.logger.info('[WebsocketClient] received!!')
32
34
  ProconBypassMan.logger.info(data)
33
35
 
34
36
  dispatch(data: data, client: client)
@@ -37,20 +39,20 @@ module ProconBypassMan
37
39
  end
38
40
 
39
41
  client.disconnected {
40
- ProconBypassMan.logger.info('websocket client: disconnected!!')
42
+ ProconBypassMan.logger.info('[WebsocketClient] disconnected!!')
41
43
  client.reconnect!
42
44
  sleep 2
43
45
  }
44
46
  client.errored { |msg|
45
- ProconBypassMan.logger.error("websocket client: errored!!, #{msg}")
47
+ ProconBypassMan.logger.error("[WebsocketClient] errored!!, #{msg}")
46
48
  client.reconnect!
47
49
  sleep 2
48
50
  }
49
51
  client.pinged { |msg|
50
- Watchdog.active!
52
+ watchdog.active!
51
53
 
52
54
  ProconBypassMan.cache.fetch key: 'ws_pinged', expires_in: 10 do
53
- ProconBypassMan.logger.info('websocket client: pinged!!')
55
+ ProconBypassMan.logger.info('[WebsocketClient] pinged!!')
54
56
  ProconBypassMan.logger.info(msg)
55
57
  end
56
58
  }
@@ -42,6 +42,10 @@ require_relative "procon_bypass_man/support/can_over_process"
42
42
  require_relative "procon_bypass_man/support/retryable"
43
43
  require_relative "procon_bypass_man/support/renice_command"
44
44
  require_relative "procon_bypass_man/support/web_connectivity_checker"
45
+ require_relative "procon_bypass_man/support/watchdog"
46
+ require_relative "procon_bypass_man/support/forever"
47
+ require_relative "procon_bypass_man/support/simple_tcp_server"
48
+ require_relative "procon_bypass_man/support/proccess_cheacker"
45
49
  require_relative "procon_bypass_man/procon_display"
46
50
  require_relative "procon_bypass_man/background"
47
51
  require_relative "procon_bypass_man/commands"
@@ -61,8 +65,6 @@ require_relative "procon_bypass_man/scheduler"
61
65
  require_relative "procon_bypass_man/plugins"
62
66
  require_relative "procon_bypass_man/worker"
63
67
  require_relative "procon_bypass_man/websocket/client"
64
- require_relative "procon_bypass_man/websocket/watchdog"
65
- require_relative "procon_bypass_man/websocket/forever"
66
68
  require_relative "procon_bypass_man/external_input"
67
69
  require_relative "procon_bypass_man/remote_action"
68
70
 
@@ -124,8 +126,6 @@ module ProconBypassMan
124
126
  return
125
127
  end
126
128
 
127
- ProconBypassMan::ExternalInput.prepare_channels
128
-
129
129
  ready_pbm
130
130
  Runner.new(gadget: gadget, procon: procon).run # ここでblockingする
131
131
  terminate_pbm
@@ -161,6 +161,12 @@ module ProconBypassMan
161
161
 
162
162
  # @return [void]
163
163
  def self.initialize_pbm
164
+ if ProconBypassMan.pid && ProconBypassMan::ProcessChecker.running?(ProconBypassMan.pid)
165
+ ProconBypassMan::SendErrorCommand.execute(error: "別のプロセスでPBMがすでに起動中なので処理を停止します。")
166
+ raise 'テスト実行中でここに入ると調査が面倒なのでエラーにします' if ENV['PBM_ENV'] == 'test'
167
+ exit 1
168
+ end
169
+
164
170
  ProconBypassMan::ReniceCommand.change_priority(to: :low, pid: $$)
165
171
  ProconBypassMan::Background::JobQueue.start!
166
172
  ProconBypassMan::Websocket::Client.start!
@@ -211,6 +217,7 @@ module ProconBypassMan
211
217
  ProconBypassMan::RemoteAction::QueueOverProcess.shutdown
212
218
  ProconBypassMan::Procon::PerformanceMeasurement::QueueOverProcess.shutdown
213
219
  self.worker&.shutdown
220
+ ProconBypassMan::ExternalInput.shutdown
214
221
  end
215
222
 
216
223
  # @return [void]
@@ -12,7 +12,7 @@ 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.7'
15
+ gem 'procon_bypass_man', '0.3.8'
16
16
  # uncomment if you want to use master branch
17
17
  # gem 'procon_bypass_man', github: 'splaplapla/procon_bypass_man', branch: 'master'
18
18
  # uncomment if you want to use serial communication feature
@@ -57,6 +57,7 @@ ProconBypassMan.configure do |config|
57
57
  # シリアル通信やTCP/IP経由で入力するときに設定してください
58
58
  # config.external_input_channels = [
59
59
  # ProconBypassMan::ExternalInput::Channels::SerialPortChannel.new(device_path: '/dev/serial0', baud_rate: 9600),
60
+ # ProconBypassMan::ExternalInput::Channels::TCPIPChannel.new(port: 9000),
60
61
  # ]
61
62
  end
62
63
 
@@ -12,7 +12,7 @@ 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.7'
15
+ gem 'procon_bypass_man', '0.3.8'
16
16
  # uncomment if you want to use master branch
17
17
  # gem 'procon_bypass_man', github: 'splaplapla/procon_bypass_man', branch: 'master'
18
18
  # uncomment if you want to use serial communication feature
@@ -62,7 +62,7 @@ ProconBypassMan.configure do |config|
62
62
  # シリアル通信やTCP/IP経由で入力するときに設定してください
63
63
  # config.external_input_channels = [
64
64
  # ProconBypassMan::ExternalInput::Channels::SerialPortChannel.new(device_path: '/dev/serial0', baud_rate: 9600),
65
- <%-# ProconBypassMan::ExternalInput::Channels::TCPIP.new(port: 9000), # TODO: まだ実装していない -%>
65
+ # ProconBypassMan::ExternalInput::Channels::TCPIPChannel.new(port: 9000),
66
66
  # ]
67
67
  end
68
68
 
data/sig/main.rbs CHANGED
@@ -210,7 +210,7 @@ class ProconBypassMan::Configuration
210
210
 
211
211
  def current_ws_server: () -> (String | nil)
212
212
 
213
- def current_ws_server_url: () -> ::String
213
+ def current_ws_server_url: () -> (String | nil)
214
214
 
215
215
  def enable_ws?: () -> bool
216
216
 
@@ -577,7 +577,7 @@ module ProconBypassMan
577
577
  @retry_on_connection_error: false
578
578
 
579
579
  def initialize: (path: String, server: untyped, ?retry_on_connection_error: false) -> void
580
- def get: -> nil
580
+ def get: -> Hash[untyped, untyped]?
581
581
  def post: (request_body: untyped) -> nil
582
582
  def put: (request_body: untyped) -> nil
583
583
 
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.7
4
+ version: 0.3.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - jiikko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-04-06 00:00:00.000000000 Z
11
+ date: 2023-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pbmenv
@@ -180,10 +180,11 @@ files:
180
180
  - lib/procon_bypass_man/device_status.rb
181
181
  - lib/procon_bypass_man/ephemeral_configuration.rb
182
182
  - lib/procon_bypass_man/external_input.rb
183
+ - lib/procon_bypass_man/external_input/boot_message.rb
183
184
  - lib/procon_bypass_man/external_input/channels.rb
184
185
  - lib/procon_bypass_man/external_input/channels/base.rb
185
186
  - 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/channels/tcpip_channel.rb
187
188
  - lib/procon_bypass_man/external_input/external_data.rb
188
189
  - lib/procon_bypass_man/plugin/splatoon2/macro/charge_tansan_bomb.rb
189
190
  - lib/procon_bypass_man/plugin/splatoon2/macro/dasei_cancel.rb
@@ -276,10 +277,12 @@ files:
276
277
  - lib/procon_bypass_man/support/cycle_sleep.rb
277
278
  - lib/procon_bypass_man/support/device_mouse_finder.rb
278
279
  - lib/procon_bypass_man/support/device_procon_finder.rb
280
+ - lib/procon_bypass_man/support/forever.rb
279
281
  - lib/procon_bypass_man/support/http_client.rb
280
282
  - lib/procon_bypass_man/support/load_agv.rb
281
283
  - lib/procon_bypass_man/support/never_exit_accidentally.rb
282
284
  - lib/procon_bypass_man/support/on_memory_cache.rb
285
+ - lib/procon_bypass_man/support/proccess_cheacker.rb
283
286
  - lib/procon_bypass_man/support/procon_performance_http_client.rb
284
287
  - lib/procon_bypass_man/support/remote_macro_http_client.rb
285
288
  - lib/procon_bypass_man/support/renice_command.rb
@@ -287,16 +290,16 @@ files:
287
290
  - lib/procon_bypass_man/support/retryable.rb
288
291
  - lib/procon_bypass_man/support/safe_timeout.rb
289
292
  - lib/procon_bypass_man/support/send_device_stats_http_client.rb
293
+ - lib/procon_bypass_man/support/simple_tcp_server.rb
290
294
  - lib/procon_bypass_man/support/update_remote_pbm_job_status_http_client.rb
291
295
  - lib/procon_bypass_man/support/uptime.rb
292
296
  - lib/procon_bypass_man/support/usb_device_controller.rb
297
+ - lib/procon_bypass_man/support/watchdog.rb
293
298
  - lib/procon_bypass_man/support/web_connectivity_checker.rb
294
299
  - lib/procon_bypass_man/support/yaml_loader.rb
295
300
  - lib/procon_bypass_man/support/yaml_writer.rb
296
301
  - lib/procon_bypass_man/version.rb
297
302
  - lib/procon_bypass_man/websocket/client.rb
298
- - lib/procon_bypass_man/websocket/forever.rb
299
- - lib/procon_bypass_man/websocket/watchdog.rb
300
303
  - lib/procon_bypass_man/worker.rb
301
304
  - procon_bypass_man.gemspec
302
305
  - project_template/README.md
@@ -1,16 +0,0 @@
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
@@ -1,47 +0,0 @@
1
- module ProconBypassMan
2
- module Websocket
3
- class Forever
4
- # 動作確認方法
5
- # - 10秒ごとにrefreshするのでタイムアウトは起きない
6
- # - ProconBypassMan::Websocket::Forever.run { loop { puts(:hi); sleep(10); ProconBypassMan::Websocket::Watchdog.active! } }
7
- # - タイムアウトが起きること
8
- # - ProconBypassMan::Websocket::Forever.run { puts(:hi); sleep(3000); }
9
- # - ブロックを1回評価するとThreadが死ぬので100秒後にタイムアウトが起きること
10
- # - ProconBypassMan::Websocket::Forever.run { puts(:hi); sleep(10); ProconBypassMan::Websocket::Watchdog.active! }
11
- def self.run(&block)
12
- loop do
13
- new.run(&block)
14
- end
15
- end
16
-
17
- def run(&block)
18
- raise("need a block") unless block_given?
19
-
20
- ws_thread = work_one(callable: block)
21
- wait_and_kill_if_outdated(ws_thread)
22
- end
23
-
24
- # @return [Thread]
25
- def work_one(callable: )
26
- Thread.start do
27
- callable.call
28
- rescue => e
29
- ProconBypassMan.logger.error("websocket client with forever: #{e.full_message}")
30
- end
31
- end
32
-
33
- def wait_and_kill_if_outdated(thread)
34
- loop do
35
- if Watchdog.outdated?
36
- Watchdog.active!
37
- ProconBypassMan.logger.error("watchdog timeout!!")
38
- thread.kill
39
- return
40
- end
41
-
42
- sleep(10)
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,19 +0,0 @@
1
- module ProconBypassMan
2
- module Websocket
3
- class Watchdog
4
- def self.outdated?
5
- @@time < Time.now
6
- end
7
-
8
- def self.time
9
- @@time
10
- end
11
-
12
- def self.active!
13
- @@time = Time.now + 100
14
- end
15
-
16
- active!
17
- end
18
- end
19
- end