wizrb 1.1.1 → 1.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +57 -0
- data/doc/devices.md +135 -0
- data/doc/lighting.md +186 -0
- data/doc/power.md +37 -0
- data/exe/wizrb +5 -0
- data/lib/wizrb/cli.rb +79 -0
- data/lib/wizrb/lighting/discover.rb +40 -0
- data/lib/wizrb/lighting/events/set_brightness_event.rb +27 -0
- data/lib/wizrb/lighting/events/set_cold_white_event.rb +27 -0
- data/lib/wizrb/lighting/events/set_color_temp_event.rb +29 -0
- data/lib/wizrb/lighting/events/set_rgb_event.rb +29 -0
- data/lib/wizrb/lighting/events/set_scene_event.rb +22 -0
- data/lib/wizrb/lighting/events/set_speed_event.rb +27 -0
- data/lib/wizrb/lighting/events/set_warm_white_event.rb +27 -0
- data/lib/wizrb/lighting/group.rb +37 -0
- data/lib/wizrb/lighting/products/dimable_light.rb +33 -0
- data/lib/wizrb/lighting/products/light.rb +64 -0
- data/lib/wizrb/lighting/products/rgb_light.rb +58 -0
- data/lib/wizrb/lighting/products/tunable_light.rb +39 -0
- data/lib/wizrb/lighting/scenes/scene.rb +70 -0
- data/lib/wizrb/lighting/scenes/spooky_scene.rb +61 -0
- data/lib/wizrb/lighting/state.rb +45 -0
- data/lib/wizrb/lighting.rb +41 -0
- data/lib/wizrb/power/discover.rb +34 -0
- data/lib/wizrb/power/group.rb +10 -0
- data/lib/wizrb/power/products/smart_plug.rb +17 -0
- data/lib/wizrb/shared/connection.rb +76 -0
- data/lib/wizrb/shared/discover.rb +116 -0
- data/lib/wizrb/shared/events/base.rb +22 -0
- data/lib/wizrb/shared/events/power_event.rb +22 -0
- data/lib/wizrb/shared/events/reboot_event.rb +15 -0
- data/lib/wizrb/shared/events/refresh_event.rb +15 -0
- data/lib/wizrb/shared/events/reset_event.rb +15 -0
- data/lib/wizrb/shared/group.rb +68 -0
- data/lib/wizrb/shared/products/device.rb +105 -0
- data/lib/wizrb/shared/state.rb +32 -0
- data/lib/wizrb/version.rb +5 -0
- data/lib/wizrb.rb +32 -0
- metadata +56 -8
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ipaddr"
|
4
|
+
require "socket"
|
5
|
+
require "json"
|
6
|
+
require_relative "group"
|
7
|
+
require_relative "products/device"
|
8
|
+
|
9
|
+
module Wizrb
|
10
|
+
module Shared
|
11
|
+
class Discover
|
12
|
+
MULTICAST_ADDR = "224.0.0.1"
|
13
|
+
BIND_ADDR = "0.0.0.0"
|
14
|
+
PORT = 38_899
|
15
|
+
REGISTRATION_MESSAGE = {
|
16
|
+
method: "registration",
|
17
|
+
params: {
|
18
|
+
phoneMac: "ABCDEFGHIJKL",
|
19
|
+
register: false,
|
20
|
+
phoneIp: "1.2.3.4",
|
21
|
+
id: "1"
|
22
|
+
}
|
23
|
+
}.to_json
|
24
|
+
|
25
|
+
def initialize(wait: 2)
|
26
|
+
@wait = wait
|
27
|
+
@listening = false
|
28
|
+
@thread = nil
|
29
|
+
@devices = []
|
30
|
+
end
|
31
|
+
|
32
|
+
def all(filters: {})
|
33
|
+
open_socket
|
34
|
+
listen_registration(filters)
|
35
|
+
dispatch_registration
|
36
|
+
sleep(@wait)
|
37
|
+
close_registration
|
38
|
+
close_socket
|
39
|
+
group_devices
|
40
|
+
end
|
41
|
+
|
42
|
+
def home(id)
|
43
|
+
all(filters: {"homeId" => id})
|
44
|
+
end
|
45
|
+
|
46
|
+
def room(id)
|
47
|
+
all(filters: {"roomId" => id})
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.all(wait: 2, filters: {})
|
51
|
+
new(wait: wait).all(filters: filters)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.home(id, wait: 2)
|
55
|
+
new(wait: wait).home(id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.room(id, wait: 2)
|
59
|
+
new(wait: wait).room(id)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def open_socket
|
65
|
+
bind_address = IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton
|
66
|
+
|
67
|
+
@socket = UDPSocket.open.tap do |socket|
|
68
|
+
socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, bind_address)
|
69
|
+
socket.setsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL, 1)
|
70
|
+
socket.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def listen_registration(filters = {})
|
75
|
+
@listening = true
|
76
|
+
|
77
|
+
@socket.bind(BIND_ADDR, PORT)
|
78
|
+
|
79
|
+
@thread = Thread.new do
|
80
|
+
while @listening
|
81
|
+
data, addr = @socket.recvfrom(65_536)
|
82
|
+
device = parse_response(data, addr)
|
83
|
+
@devices << device if device && (filters.to_a - device.system_config.to_a).empty?
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def dispatch_registration
|
89
|
+
@socket.send(REGISTRATION_MESSAGE, 0, MULTICAST_ADDR, PORT)
|
90
|
+
end
|
91
|
+
|
92
|
+
def close_registration
|
93
|
+
@listening = false
|
94
|
+
@thread.terminate
|
95
|
+
end
|
96
|
+
|
97
|
+
def close_socket
|
98
|
+
@socket.close
|
99
|
+
@socket = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_response(data, addr)
|
103
|
+
response = JSON.parse(data)
|
104
|
+
return unless response.dig("result", "success") && addr[1] && addr[2]
|
105
|
+
|
106
|
+
Wizrb::Shared::Products::Device.new(ip: addr[2], port: addr[1])
|
107
|
+
rescue
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def group_devices
|
112
|
+
Wizrb::Shared::Group.new(devices: @devices)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Shared
|
7
|
+
module Events
|
8
|
+
class Base
|
9
|
+
attr_reader :params
|
10
|
+
|
11
|
+
def initialize(method:, params: {})
|
12
|
+
@method = method
|
13
|
+
@params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_json(*_args)
|
17
|
+
@to_json ||= {method: @method, params: @params}.to_json
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Shared
|
7
|
+
module Events
|
8
|
+
class PowerEvent < Wizrb::Shared::Events::Base
|
9
|
+
def initialize(value)
|
10
|
+
validate!(value)
|
11
|
+
super(method: "setPilot", params: {state: value})
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate!(value)
|
17
|
+
raise ArgumentError, "Power state must be a boolean" unless [true, false].include?(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "events/base"
|
4
|
+
require_relative "events/power_event"
|
5
|
+
require_relative "events/reboot_event"
|
6
|
+
require_relative "events/refresh_event"
|
7
|
+
require_relative "events/reset_event"
|
8
|
+
|
9
|
+
module Wizrb
|
10
|
+
module Shared
|
11
|
+
class Group
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
attr_reader :devices
|
15
|
+
|
16
|
+
def initialize(devices:)
|
17
|
+
@devices = devices
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&)
|
21
|
+
@devices.each(&)
|
22
|
+
end
|
23
|
+
|
24
|
+
def power_on
|
25
|
+
dispatch_event(Wizrb::Shared::Events::PowerEvent.new(true))
|
26
|
+
end
|
27
|
+
|
28
|
+
def power_off
|
29
|
+
dispatch_event(Wizrb::Shared::Events::PowerEvent.new(false))
|
30
|
+
end
|
31
|
+
|
32
|
+
def reboot
|
33
|
+
dispatch_event(Wizrb::Shared::Events::RebootEvent.new)
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset
|
37
|
+
dispatch_event(Wizrb::Shared::Events::ResetEvent.new)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dispatch_event(event)
|
41
|
+
unless event.is_a?(Wizrb::Shared::Events::Base)
|
42
|
+
raise ArgumentError, "Not an instance of Wizrb::Shared::Events::Base"
|
43
|
+
end
|
44
|
+
|
45
|
+
dispatch(event)
|
46
|
+
end
|
47
|
+
|
48
|
+
def dispatch_events(*events)
|
49
|
+
events.each do |event|
|
50
|
+
unless event.is_a?(Wizrb::Shared::Events::Base)
|
51
|
+
raise ArgumentError, "Not an instance of Wizrb::Shared::Events::Base"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
params = events.reduce({}) { |h, e| h.merge(e.params) }
|
56
|
+
dispatch_event(Wizrb::Shared::Events::Base.new(method: "setState", params: params))
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def dispatch(event)
|
62
|
+
@devices.each do |light|
|
63
|
+
light.dispatch_event(event)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "socket"
|
5
|
+
require_relative "../connection"
|
6
|
+
require_relative "../state"
|
7
|
+
require_relative "../events/base"
|
8
|
+
require_relative "../events/power_event"
|
9
|
+
require_relative "../events/reboot_event"
|
10
|
+
require_relative "../events/refresh_event"
|
11
|
+
require_relative "../events/reset_event"
|
12
|
+
|
13
|
+
module Wizrb
|
14
|
+
module Shared
|
15
|
+
module Products
|
16
|
+
class Device
|
17
|
+
attr_reader :ip, :port, :state
|
18
|
+
|
19
|
+
def initialize(ip:, port: 38_899, state: Wizrb::Shared::State.new)
|
20
|
+
@ip = ip
|
21
|
+
@port = port
|
22
|
+
@state = state
|
23
|
+
connect
|
24
|
+
end
|
25
|
+
|
26
|
+
def system_config
|
27
|
+
@system_config ||= dispatch({method: "getSystemConfig", params: {}})&.dig("result")
|
28
|
+
end
|
29
|
+
|
30
|
+
def model_config
|
31
|
+
@model_config ||= dispatch({method: "getModelConfig", params: {}})&.dig("result")
|
32
|
+
end
|
33
|
+
|
34
|
+
def user_config
|
35
|
+
@user_config ||= dispatch({method: "getUserConfig", params: {}})&.dig("result")
|
36
|
+
end
|
37
|
+
|
38
|
+
def module_name
|
39
|
+
@module_name ||= system_config&.dig("moduleName")
|
40
|
+
end
|
41
|
+
|
42
|
+
def power_on
|
43
|
+
dispatch_event(Wizrb::Shared::Events::PowerEvent.new(true))
|
44
|
+
end
|
45
|
+
|
46
|
+
def power_off
|
47
|
+
dispatch_event(Wizrb::Shared::Events::PowerEvent.new(false))
|
48
|
+
end
|
49
|
+
|
50
|
+
def power_switch
|
51
|
+
if refresh.state.power
|
52
|
+
power_off
|
53
|
+
else
|
54
|
+
power_on
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def reboot
|
59
|
+
dispatch_event(Wizrb::Shared::Events::RebootEvent.new)
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset
|
63
|
+
dispatch_event(Wizrb::Shared::Events::ResetEvent.new)
|
64
|
+
end
|
65
|
+
|
66
|
+
def refresh
|
67
|
+
response = dispatch_event(Wizrb::Shared::Events::RefreshEvent.new)
|
68
|
+
state.parse!(response)
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def dispatch_event(event)
|
73
|
+
unless event.is_a?(Wizrb::Shared::Events::Base)
|
74
|
+
raise ArgumentError, "Not an instance of Wizrb::Shared::Events::Base"
|
75
|
+
end
|
76
|
+
|
77
|
+
dispatch(event)
|
78
|
+
end
|
79
|
+
|
80
|
+
def dispatch_events(*events)
|
81
|
+
events.each do |event|
|
82
|
+
unless event.is_a?(Wizrb::Shared::Events::Base)
|
83
|
+
raise ArgumentError, "Not an instance of Wizrb::Shared::Events::Base"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
params = events.reduce({}) { |h, e| h.merge(e.params) }
|
88
|
+
dispatch_event(Wizrb::Shared::Events::Base.new(method: "setState", params: params))
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def connect
|
94
|
+
@connection = Wizrb::Shared::Connection.new(ip, port)
|
95
|
+
refresh
|
96
|
+
end
|
97
|
+
|
98
|
+
def dispatch(data)
|
99
|
+
@connection.send(data)
|
100
|
+
@connection.receive(timeout: 5, max: 65_536)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wizrb
|
4
|
+
module Shared
|
5
|
+
class State
|
6
|
+
STATE_KEYS = %i[state].freeze
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@state = {state: false}
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse!(response)
|
13
|
+
result = response&.dig("result")
|
14
|
+
return unless result
|
15
|
+
|
16
|
+
@state = result.transform_keys(&:to_sym).slice(*STATE_KEYS)
|
17
|
+
end
|
18
|
+
|
19
|
+
def power
|
20
|
+
@state[:state]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
@state.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_json(*_args)
|
28
|
+
@state.to_json
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/wizrb.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "wizrb/version"
|
4
|
+
require_relative "wizrb/shared/connection"
|
5
|
+
require_relative "wizrb/shared/discover"
|
6
|
+
require_relative "wizrb/shared/group"
|
7
|
+
require_relative "wizrb/shared/state"
|
8
|
+
require_relative "wizrb/lighting"
|
9
|
+
require_relative "wizrb/lighting/discover"
|
10
|
+
require_relative "wizrb/lighting/group"
|
11
|
+
require_relative "wizrb/lighting/state"
|
12
|
+
require_relative "wizrb/power/discover"
|
13
|
+
require_relative "wizrb/power/group"
|
14
|
+
|
15
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/shared/products/*.rb"].sort.each { |file| require file }
|
16
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/shared/events/*.rb"].sort.each { |file| require file }
|
17
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/lighting/products/*.rb"].sort.each { |file| require file }
|
18
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/lighting/events/*.rb"].sort.each { |file| require file }
|
19
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/lighting/scenes/*.rb"].sort.each { |file| require file }
|
20
|
+
Dir["#{File.dirname(__FILE__)}/wizrb/power/products/*.rb"].sort.each { |file| require file }
|
21
|
+
|
22
|
+
module Wizrb
|
23
|
+
class Error < StandardError; end
|
24
|
+
|
25
|
+
class ConnectionError < Wizrb::Error; end
|
26
|
+
|
27
|
+
class ConnectionTimeoutError < Wizrb::ConnectionError
|
28
|
+
def initialize(msg = "Connection timeout waiting for response.")
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wizrb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bert McCutchen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -16,21 +16,68 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.3'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.3.1
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - "~>"
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
29
|
+
version: '1.3'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.3.1
|
27
33
|
description: Manage the state of your Philips WiZ devices.
|
28
34
|
email:
|
29
35
|
- mail@bertm.dev
|
30
|
-
executables:
|
36
|
+
executables:
|
37
|
+
- wizrb
|
31
38
|
extensions: []
|
32
39
|
extra_rdoc_files: []
|
33
|
-
files:
|
40
|
+
files:
|
41
|
+
- CHANGELOG.md
|
42
|
+
- LICENSE.txt
|
43
|
+
- README.md
|
44
|
+
- doc/devices.md
|
45
|
+
- doc/lighting.md
|
46
|
+
- doc/power.md
|
47
|
+
- exe/wizrb
|
48
|
+
- lib/wizrb.rb
|
49
|
+
- lib/wizrb/cli.rb
|
50
|
+
- lib/wizrb/lighting.rb
|
51
|
+
- lib/wizrb/lighting/discover.rb
|
52
|
+
- lib/wizrb/lighting/events/set_brightness_event.rb
|
53
|
+
- lib/wizrb/lighting/events/set_cold_white_event.rb
|
54
|
+
- lib/wizrb/lighting/events/set_color_temp_event.rb
|
55
|
+
- lib/wizrb/lighting/events/set_rgb_event.rb
|
56
|
+
- lib/wizrb/lighting/events/set_scene_event.rb
|
57
|
+
- lib/wizrb/lighting/events/set_speed_event.rb
|
58
|
+
- lib/wizrb/lighting/events/set_warm_white_event.rb
|
59
|
+
- lib/wizrb/lighting/group.rb
|
60
|
+
- lib/wizrb/lighting/products/dimable_light.rb
|
61
|
+
- lib/wizrb/lighting/products/light.rb
|
62
|
+
- lib/wizrb/lighting/products/rgb_light.rb
|
63
|
+
- lib/wizrb/lighting/products/tunable_light.rb
|
64
|
+
- lib/wizrb/lighting/scenes/scene.rb
|
65
|
+
- lib/wizrb/lighting/scenes/spooky_scene.rb
|
66
|
+
- lib/wizrb/lighting/state.rb
|
67
|
+
- lib/wizrb/power/discover.rb
|
68
|
+
- lib/wizrb/power/group.rb
|
69
|
+
- lib/wizrb/power/products/smart_plug.rb
|
70
|
+
- lib/wizrb/shared/connection.rb
|
71
|
+
- lib/wizrb/shared/discover.rb
|
72
|
+
- lib/wizrb/shared/events/base.rb
|
73
|
+
- lib/wizrb/shared/events/power_event.rb
|
74
|
+
- lib/wizrb/shared/events/reboot_event.rb
|
75
|
+
- lib/wizrb/shared/events/refresh_event.rb
|
76
|
+
- lib/wizrb/shared/events/reset_event.rb
|
77
|
+
- lib/wizrb/shared/group.rb
|
78
|
+
- lib/wizrb/shared/products/device.rb
|
79
|
+
- lib/wizrb/shared/state.rb
|
80
|
+
- lib/wizrb/version.rb
|
34
81
|
homepage: https://github.com/bert-mccutchen/wizrb
|
35
82
|
licenses:
|
36
83
|
- MIT
|
@@ -38,6 +85,7 @@ metadata:
|
|
38
85
|
homepage_uri: https://github.com/bert-mccutchen/wizrb
|
39
86
|
source_code_uri: https://github.com/bert-mccutchen/wizrb
|
40
87
|
changelog_uri: https://github.com/bert-mccutchen/wizrb/blob/master/CHANGELOG.md
|
88
|
+
rubygems_mfa_required: 'true'
|
41
89
|
post_install_message:
|
42
90
|
rdoc_options: []
|
43
91
|
require_paths:
|
@@ -46,14 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
94
|
requirements:
|
47
95
|
- - ">="
|
48
96
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
97
|
+
version: '3.1'
|
50
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
99
|
requirements:
|
52
100
|
- - ">="
|
53
101
|
- !ruby/object:Gem::Version
|
54
102
|
version: '0'
|
55
103
|
requirements: []
|
56
|
-
rubygems_version: 3.
|
104
|
+
rubygems_version: 3.5.11
|
57
105
|
signing_key:
|
58
106
|
specification_version: 4
|
59
107
|
summary: State management for Philips WiZ devices.
|