procon_bypass_man 0.3.7 → 0.3.8

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/.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