procon_bypass_man 0.1.16 → 0.1.19

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +86 -11
  3. data/.github/workflows/release.yml +2 -2
  4. data/.rubocop.yml +2 -0
  5. data/CHANGELOG.md +19 -1
  6. data/Gemfile +1 -0
  7. data/Gemfile.lock +7 -3
  8. data/README.md +39 -15
  9. data/Steepfile +31 -17
  10. data/docs/getting_started.md +60 -0
  11. data/docs/setting/left-analogstick-cap.md +60 -0
  12. data/docs/setting/splatoon2_macro_sokuwari_bubble.md +52 -0
  13. data/docs/setup_raspi.md +2 -0
  14. data/docs/setup_raspi.mitamae.rb +7 -8
  15. data/docs/setup_raspi_by_mitamae.md +37 -1
  16. data/docs/upgrade_pbm.md +56 -0
  17. data/lib/ext/module.rb +16 -0
  18. data/lib/procon_bypass_man/buttons_setting_configuration/layer.rb +33 -7
  19. data/lib/procon_bypass_man/buttons_setting_configuration/loader.rb +4 -1
  20. data/lib/procon_bypass_man/buttons_setting_configuration/validator.rb +36 -0
  21. data/lib/procon_bypass_man/buttons_setting_configuration.rb +34 -21
  22. data/lib/procon_bypass_man/bypass/usb_hid_logger.rb +6 -4
  23. data/lib/procon_bypass_man/commands/bypass_command.rb +2 -2
  24. data/lib/procon_bypass_man/configuration.rb +5 -1
  25. data/lib/procon_bypass_man/domains/binary/base.rb +14 -0
  26. data/lib/procon_bypass_man/domains/binary/inbound_procon_binary.rb +0 -14
  27. data/lib/procon_bypass_man/domains/binary/processing_procon_binary.rb +2 -16
  28. data/lib/procon_bypass_man/plugin/splatoon2/macro/fast_return.rb +1 -1
  29. data/lib/procon_bypass_man/plugin/splatoon2/macro/jump_to_left_key.rb +1 -1
  30. data/lib/procon_bypass_man/plugin/splatoon2/macro/jump_to_right_key.rb +1 -1
  31. data/lib/procon_bypass_man/plugin/splatoon2/macro/jump_to_up_key.rb +1 -1
  32. data/lib/procon_bypass_man/plugin/splatoon2/macro/sokuwari_for_splash_bomb.rb +22 -0
  33. data/lib/procon_bypass_man/plugin/splatoon2/version.rb +1 -1
  34. data/lib/procon_bypass_man/plugins.rb +1 -0
  35. data/lib/procon_bypass_man/procon/button.rb +1 -1
  36. data/lib/procon_bypass_man/procon/button_collection.rb +8 -0
  37. data/lib/procon_bypass_man/procon/macro.rb +89 -0
  38. data/lib/procon_bypass_man/procon/macro_builder.rb +123 -0
  39. data/lib/procon_bypass_man/procon/macro_registry.rb +9 -27
  40. data/lib/procon_bypass_man/procon/mode_registry.rb +4 -4
  41. data/lib/procon_bypass_man/procon/press_button_aware.rb +6 -5
  42. data/lib/procon_bypass_man/procon/user_operation.rb +16 -2
  43. data/lib/procon_bypass_man/procon/value_objects/analog_stick.rb +1 -1
  44. data/lib/procon_bypass_man/procon.rb +6 -4
  45. data/lib/procon_bypass_man/remote_pbm_action/restore_pbm_setting.rb +3 -3
  46. data/lib/procon_bypass_man/support/compress_array.rb +5 -0
  47. data/lib/procon_bypass_man/support/http_client.rb +4 -0
  48. data/lib/procon_bypass_man/support/on_memory_cache.rb +3 -1
  49. data/lib/procon_bypass_man/support/server_pool.rb +4 -0
  50. data/lib/procon_bypass_man/support/yaml_writer.rb +9 -0
  51. data/lib/procon_bypass_man/version.rb +1 -1
  52. data/lib/procon_bypass_man/websocket/pbm_job_client.rb +13 -2
  53. data/lib/procon_bypass_man.rb +2 -0
  54. data/procon_bypass_man.gemspec +1 -1
  55. data/project_template/README.md +10 -5
  56. data/project_template/app.rb +3 -2
  57. data/sig/main.rbs +213 -42
  58. data/sig/on_memory_cache.rbs +16 -0
  59. metadata +15 -5
@@ -0,0 +1,89 @@
1
+ class ProconBypassMan::Procon::Macro
2
+ class NestedStep
3
+ def initialize(value)
4
+ @hash = value
5
+ unless @hash[:end_at]
6
+ @hash[:end_at] = (Time.now + @hash[:continue_for]).round(4)
7
+ end
8
+ end
9
+
10
+ def over_end_at?
11
+ (@hash[:end_at] < Time.now).tap do |result|
12
+ if result
13
+ ProconBypassMan.logger.debug { "[Macro] nested step is finished(#{@hash})" }
14
+ end
15
+ end
16
+ end
17
+
18
+ def next_step
19
+ incr_step_index!
20
+
21
+ debug_incr_called_count!
22
+ if step = current_step
23
+ return step
24
+ else
25
+ reset_step_index!
26
+ return current_step
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def current_step
33
+ @hash[:steps][step_index]
34
+ end
35
+
36
+ def step_index
37
+ @hash[:step_index]
38
+ end
39
+
40
+ def incr_step_index!
41
+ if step_index
42
+ @hash[:step_index] += 1
43
+ else
44
+ @hash[:step_index] = 0
45
+ end
46
+ end
47
+
48
+ def reset_step_index!
49
+ @hash[:step_index] = 0
50
+ end
51
+
52
+ def debug_incr_called_count!
53
+ @hash[:debug_called_count] ||= 0
54
+ @hash[:debug_called_count] += 1
55
+ end
56
+ end
57
+
58
+ attr_accessor :name, :steps
59
+
60
+ def initialize(name: , steps: )
61
+ self.name = name
62
+ self.steps = steps
63
+ end
64
+
65
+ def next_step
66
+ step = steps.first
67
+ if step.is_a?(Symbol)
68
+ return steps.shift
69
+ end
70
+
71
+ if step.is_a?(Hash)
72
+ nested_step = NestedStep.new(step)
73
+ if nested_step.over_end_at?
74
+ steps.shift # NestedStepを破棄する
75
+ return next_step
76
+ else
77
+ return nested_step.next_step
78
+ end
79
+ end
80
+ end
81
+
82
+ def finished?
83
+ steps.empty?
84
+ end
85
+
86
+ def ongoing?
87
+ !finished?
88
+ end
89
+ end
@@ -0,0 +1,123 @@
1
+ class ProconBypassMan::Procon::MacroBuilder
2
+ class SubjectMerger
3
+ def self.merge(subjects)
4
+ if subjects.size == 1
5
+ return subjects.first.to_steps
6
+ end
7
+
8
+ base = subjects.first
9
+ remain = subjects[1..-1]
10
+ remain.map { |x| base.to_steps.zip(x.to_steps) }.first
11
+ end
12
+ end
13
+
14
+ class Subject
15
+ def initialize(value)
16
+ @button =
17
+ if match = value.match(/_(\w+)\z/)
18
+ match[1]
19
+ else
20
+ :unknown
21
+ end
22
+ @type =
23
+ if value.start_with?("toggle_")
24
+ :toggle
25
+ else
26
+ :pressing
27
+ end
28
+ end
29
+
30
+ def toggle?
31
+ @type == :toggle
32
+ end
33
+
34
+ def pressing?
35
+ not toggle?
36
+ end
37
+
38
+ def to_steps
39
+ case @type
40
+ when :toggle
41
+ [@button.to_sym, :none]
42
+ when :pressing
43
+ [@button.to_sym, @button.to_sym]
44
+ end
45
+ end
46
+ end
47
+
48
+ RESERVED_WORD_NONE = :none
49
+ RESERVED_WORDS = {
50
+ RESERVED_WORD_NONE => true,
51
+ }
52
+
53
+ def initialize(steps)
54
+ @steps = steps.map(&:to_s)
55
+ end
56
+
57
+ # @return [Arary<Symbol>]
58
+ def build
59
+ steps = @steps.map { |step|
60
+ if is_reserved?(step: step) || v1_format?(step: step)
61
+ step.to_sym
62
+ elsif value = build_if_v2_format?(step: step)
63
+ value
64
+ else
65
+ nil
66
+ end
67
+ }
68
+ steps.compact.flatten
69
+ end
70
+
71
+ private
72
+
73
+ def is_reserved?(step: )
74
+ RESERVED_WORDS[step.to_sym]
75
+ end
76
+
77
+ def v1_format?(step: )
78
+ if is_button(step)
79
+ step
80
+ end
81
+ end
82
+
83
+ def build_if_v2_format?(step: )
84
+ # 時間指定なし
85
+ if(match = step.match(%r!\Atoggle_(\w+)\z!)) && (button_candidate = match[1]) && is_button(button_candidate)
86
+ button = button_candidate
87
+ return [button.to_sym, :none]
88
+ end
89
+
90
+ # 時間指定あり
91
+ if %r!^(pressing_|toggle_)! =~ step && (subjects = step.scan(%r!pressing_[^_]+|toggle_[^_]+!)) && (match = step.match(%r!_for_([\d_]+)(sec)?\z!))
92
+ sec = match[1]
93
+ return [
94
+ { continue_for: to_num(sec),
95
+ steps: SubjectMerger.merge(subjects.map { |x| Subject.new(x) }),
96
+ }
97
+ ]
98
+ end
99
+
100
+ # no-op command
101
+ if(match = step.match(%r!wait_for_([\d_]+)(sec)?\z!))
102
+ sec = match[1]
103
+ return [
104
+ { continue_for: to_num(sec),
105
+ steps: [:none],
106
+ }
107
+ ]
108
+ end
109
+ end
110
+
111
+ # @return [Boolean]
112
+ def is_button(step)
113
+ !!ProconBypassMan::Procon::ButtonCollection::BUTTONS_MAP[step.to_sym]
114
+ end
115
+
116
+ def to_num(value)
117
+ if value.include?("_")
118
+ value.sub("_", ".").to_f
119
+ else
120
+ value.to_i
121
+ end
122
+ end
123
+ end
@@ -1,39 +1,21 @@
1
1
  class ProconBypassMan::Procon::MacroRegistry
2
- class Macro
3
- attr_accessor :name, :steps
4
-
5
- def initialize(name: , steps: )
6
- self.name = name
7
- self.steps = steps
8
- end
9
-
10
- def next_step
11
- steps.shift
12
- end
13
-
14
- def finished?
15
- steps.empty?
16
- end
17
-
18
- def ongoing?
19
- !finished?
20
- end
21
- end
22
-
23
2
  PRESETS = {
24
3
  null: [],
25
4
  }
26
5
 
27
- def self.install_plugin(klass)
28
- if plugins[klass.name]
29
- raise "すでに登録済みです"
6
+ def self.install_plugin(klass, steps: nil)
7
+ if plugins[klass.to_s.to_sym]
8
+ raise "#{klass} macro is already registered"
30
9
  end
31
- plugins[klass.name] = klass.steps
10
+
11
+ plugins[klass.to_s.to_sym] = ->{
12
+ ProconBypassMan::Procon::MacroBuilder.new(steps || klass.steps).build
13
+ }
32
14
  end
33
15
 
34
16
  def self.load(name)
35
- steps = PRESETS[name] || plugins[name] || raise("unknown macro")
36
- Macro.new(name: name, steps: steps.dup)
17
+ steps = PRESETS[name] || plugins[name].call || raise("unknown macro")
18
+ ProconBypassMan::Procon::Macro.new(name: name, steps: steps.dup)
37
19
  end
38
20
 
39
21
  def self.reset!
@@ -23,14 +23,14 @@ class ProconBypassMan::Procon::ModeRegistry
23
23
  }
24
24
 
25
25
  def self.install_plugin(klass)
26
- if plugins[klass.name.to_sym]
27
- raise "すでに登録済みです"
26
+ if plugins[klass.to_s.to_sym]
27
+ raise "#{klass} mode is already registered"
28
28
  end
29
- plugins[klass.name.to_sym] = klass.binaries
29
+ plugins[klass.to_s.to_sym] = ->{ klass.binaries }
30
30
  end
31
31
 
32
32
  def self.load(name)
33
- b = PRESETS[name] || plugins[name] || raise("unknown mode")
33
+ b = PRESETS[name] || plugins[name]&.call || raise("#{name} is unknown mode")
34
34
  Mode.new(name: name, binaries: b.dup)
35
35
  end
36
36
 
@@ -1,14 +1,15 @@
1
1
  class ProconBypassMan::PressButtonAware
2
+ BIT_ON = '1'.freeze
3
+
2
4
  def initialize(binary)
3
5
  @binary = binary
4
6
  end
5
7
 
8
+ # @param [Symbol]
9
+ # @return [Boolean]
6
10
  def pressing_button?(button)
7
11
  button_obj = ProconBypassMan::Procon::Button.new(button)
8
- @binary[
9
- button_obj.byte_position
10
- ].unpack("H*").first.to_i(16).to_s(2).reverse[
11
- button_obj.bit_position
12
- ] == '1'
12
+ byte = @binary[button_obj.byte_position].unpack("C").first.to_s(2).reverse
13
+ byte[button_obj.bit_position] == BIT_ON
13
14
  end
14
15
  end
@@ -33,9 +33,23 @@ class ProconBypassMan::Procon::UserOperation
33
33
  binary.write_as_press_button(button)
34
34
  end
35
35
 
36
- # @param [Symbol] button
36
+ # @param [Symbol, Array<Symbol>] button
37
37
  def press_button_only(button)
38
- binary.write_as_press_button_only(button)
38
+ if button.is_a?(Array)
39
+ binary.set_no_action!
40
+ button.uniq.each do |b|
41
+ unless ProconBypassMan::Procon::MacroBuilder::RESERVED_WORD_NONE == b
42
+ binary.write_as_press_button(b)
43
+ end
44
+ end
45
+ return
46
+ end
47
+
48
+ if ProconBypassMan::Procon::MacroBuilder::RESERVED_WORD_NONE == button
49
+ binary.set_no_action!
50
+ else
51
+ binary.write_as_press_button_only(button)
52
+ end
39
53
  end
40
54
 
41
55
  # @return [void]
@@ -5,7 +5,7 @@ class ProconBypassMan::Procon::AnalogStick
5
5
  def initialize(binary: )
6
6
  @neutral_position = ProconBypassMan::ButtonsSettingConfiguration.instance.neutral_position
7
7
  bytes = binary[ProconBypassMan::Procon::ButtonCollection::LEFT_ANALOG_STICK.fetch(:byte_position)]
8
- byte6, byte7, byte8 = bytes.each_char.map { |x| x.unpack("H*").first.to_i(16).to_s(2).rjust(8, "0") }
8
+ byte6, byte7, byte8 = bytes.each_char.map { |x| x.unpack("C").first.to_s(2).rjust(8, "0") }
9
9
 
10
10
  self.bin_x = "#{byte7[4..7]}#{byte6}"
11
11
  self.bin_y = "#{byte8}#{byte7[0..3]}"
@@ -1,7 +1,9 @@
1
1
  class ProconBypassMan::Procon
2
2
  require "procon_bypass_man/procon/consts"
3
3
  require "procon_bypass_man/procon/mode_registry"
4
+ require "procon_bypass_man/procon/macro"
4
5
  require "procon_bypass_man/procon/macro_registry"
6
+ require "procon_bypass_man/procon/macro_builder"
5
7
  require "procon_bypass_man/procon/layer_changer"
6
8
  require "procon_bypass_man/procon/button_collection"
7
9
  require "procon_bypass_man/procon/user_operation"
@@ -102,12 +104,12 @@ class ProconBypassMan::Procon
102
104
  user_operation.unpress_button(button)
103
105
  end
104
106
 
105
- current_layer.left_analog_stick_caps.each do |buttons, options|
106
- if buttons.nil? || user_operation.pressing_all_buttons?(buttons)
107
- options[:force_neutral]&.each do |force_neutral_button|
107
+ current_layer.left_analog_stick_caps.each do |config|
108
+ if config[:if_pressed].nil? || user_operation.pressing_all_buttons?(config[:if_pressed])
109
+ config[:force_neutral]&.each do |force_neutral_button|
108
110
  user_operation.unpress_button(force_neutral_button)
109
111
  end
110
- user_operation.apply_left_analog_stick_cap(cap: options[:cap])
112
+ user_operation.apply_left_analog_stick_cap(cap: config[:cap])
111
113
  end
112
114
  end
113
115
 
@@ -6,9 +6,9 @@ module ProconBypassMan
6
6
  require "pbmenv"
7
7
  ProconBypassMan.logger.info "execute RestorePbmSettingAction!"
8
8
  setting = args.dig("setting") or raise(ProconBypassMan::RemotePbmAction::NeedPbmVersionError, "settingが必要です, #{args.inspect}")
9
- File.write(
10
- ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path,
11
- setting.to_yaml,
9
+ ProconBypassMan::YamlWriter.execute(
10
+ path: ProconBypassMan::ButtonsSettingConfiguration.instance.setting_path,
11
+ content: setting,
12
12
  )
13
13
  ProconBypassMan.hot_reload!
14
14
  end
@@ -54,3 +54,8 @@ module ProconBypassMan
54
54
  end
55
55
  end
56
56
  end
57
+
58
+
59
+ if $0 == __FILE__
60
+ ProconBypassMan::CompressArray.new([''])
61
+ end
@@ -100,3 +100,7 @@ module ProconBypassMan
100
100
  end
101
101
  end
102
102
  end
103
+
104
+ if $0 == __FILE__
105
+ ProconBypassMan::HttpClient.new(path: '/', server_pool: nil, retry_on_connection_error: false).get(response_body: '')
106
+ end
@@ -1,6 +1,8 @@
1
1
  class ProconBypassMan::OnMemoryCache
2
2
  class CacheValue
3
- attr_accessor :expired_at, :value
3
+ # @param [Time]
4
+ attr_accessor :expired_at
5
+ attr_accessor :value
4
6
 
5
7
  def initialize(expired_at: , value: )
6
8
  self.expired_at = expired_at
@@ -40,3 +40,7 @@ module ProconBypassMan
40
40
  end
41
41
  end
42
42
  end
43
+
44
+ if $0 == __FILE__
45
+ ProconBypassMan::ServerPool.new(servers: ['http://example.com'])
46
+ end
@@ -0,0 +1,9 @@
1
+ class ProconBypassMan::YamlWriter
2
+ # @return [void]
3
+ def self.write(path: , content: )
4
+ File.write(
5
+ path,
6
+ content.gsub("\r\n", "\n").to_yaml,
7
+ )
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ProconBypassMan
4
- VERSION = "0.1.16"
4
+ VERSION = "0.1.19"
5
5
  end
@@ -32,7 +32,9 @@ module ProconBypassMan
32
32
  }
33
33
 
34
34
  client.received do |data|
35
- validate_and_run(data: data)
35
+ ProconBypassMan.logger.info(data)
36
+
37
+ dispatch(data: data, client: client)
36
38
  rescue => e
37
39
  ProconBypassMan::SendErrorCommand.execute(error: e)
38
40
  end
@@ -51,11 +53,20 @@ module ProconBypassMan
51
53
  end
52
54
  end
53
55
 
56
+ # @param [Hash] data
57
+ def self.dispatch(data: , client: )
58
+ pbm_job_hash = data.dig("message")
59
+ if pbm_job_hash['action'] == "ping"
60
+ client.perform('pong', { device_id: ProconBypassMan.device_id, message: 'hello from pbm' })
61
+ else
62
+ validate_and_run(data: data)
63
+ end
64
+ end
65
+
54
66
  # @raise [ProconBypassMan::RemotePbmActionObject::ValidationError]
55
67
  # @param [Hash] data
56
68
  # @return [Void]
57
69
  def self.validate_and_run(data: )
58
- ProconBypassMan.logger.debug { data }
59
70
  pbm_job_hash = data.dig("message")
60
71
  begin
61
72
  pbm_job_object = ProconBypassMan::RemotePbmActionObject.new(action: pbm_job_hash["action"],
@@ -7,11 +7,13 @@ require "securerandom"
7
7
  require 'em/pure_ruby'
8
8
  require "action_cable_client"
9
9
  require "ext/em_pure_ruby"
10
+ require "ext/module"
10
11
 
11
12
  require_relative "procon_bypass_man/version"
12
13
  require_relative "procon_bypass_man/remote_pbm_action"
13
14
  require_relative "procon_bypass_man/support/signal_handler"
14
15
  require_relative "procon_bypass_man/support/callbacks"
16
+ require_relative "procon_bypass_man/support/yaml_writer"
15
17
  require_relative "procon_bypass_man/support/safe_timeout"
16
18
  require_relative "procon_bypass_man/support/compress_array"
17
19
  require_relative "procon_bypass_man/support/uptime"
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["jiikko"]
9
9
  spec.email = ["n905i.1214@gmail.com"]
10
10
 
11
- spec.summary = "An extension for Nintendo Switch Pro Controller"
11
+ spec.summary = "A programmable converter for Nintendo Switch Pro Controller"
12
12
  spec.description = spec.summary
13
13
  spec.homepage = "https://github.com/splaplapla/procon_bypass_man"
14
14
  spec.license = "MIT"
@@ -1,11 +1,16 @@
1
1
  # Project Template
2
- これらは https://github.com/splaplapla/pbmenv がinstallするときに配備するファイルです
2
+ * これらは https://github.com/splaplapla/pbmenv がinstallするときに配備するファイルです
3
+ * pbm_webはオプショナルです
3
4
 
4
5
  ## systemdを使ってサービスに登録する方法
5
- * sudo ln -s /usr/share/pbm/current/systemd_units/pbm.service /etc/systemd/system/pbm.service
6
- * sudo ln -s /usr/share/pbm/current/systemd_units/pbm_web.service /etc/systemd/system/pbm_web.service
7
- * sudo systemctl enable pbm.service
8
- * sudo systemctl enable pbm_web.service
6
+ systemctl enableした後は、次回のOS起動時にserviceも自動起動します
7
+
8
+ * pbm
9
+ * sudo systemctl link /usr/share/pbm/current/systemd_units/pbm.service
10
+ * sudo systemctl enable pbm.service
11
+ * pbm_web
12
+ * sudo systemctl link /usr/share/pbm/current/systemd_units/pbm_web.service
13
+ * sudo systemctl enable pbm_web.service
9
14
 
10
15
  ## systemdを使ってサービスから解除する方法
11
16
  * sudo systemctl disable pbm.service
@@ -5,14 +5,15 @@ require 'bundler/inline'
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
7
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
8
- gem 'procon_bypass_man', '0.1.16'
8
+ gem 'procon_bypass_man', '0.1.19'
9
9
  end
10
10
 
11
11
  ProconBypassMan.configure do |config|
12
12
  config.root = File.expand_path(__dir__)
13
13
  config.logger = Logger.new("#{ProconBypassMan.root}/app.log", 5, 1024 * 1024 * 10)
14
14
  config.logger.level = :debug
15
- # config.api_servers = ['https://...']
15
+ # webからProconBypassManを操作できるwebサービス
16
+ # config.api_servers = ['https://pbm-cloud.herokuapp.com']
16
17
  config.enable_critical_error_logging = true
17
18
  end
18
19