wizrb 1.1.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +62 -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 +55 -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 +68 -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 +45 -4
@@ -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 StandardError
|
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(&block)
|
21
|
+
@devices.each(&block)
|
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.1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bert McCutchen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-02-
|
11
|
+
date: 2022-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -27,10 +27,51 @@ dependencies:
|
|
27
27
|
description: Manage the state of your Philips WiZ devices.
|
28
28
|
email:
|
29
29
|
- mail@bertm.dev
|
30
|
-
executables:
|
30
|
+
executables:
|
31
|
+
- wizrb
|
31
32
|
extensions: []
|
32
33
|
extra_rdoc_files: []
|
33
|
-
files:
|
34
|
+
files:
|
35
|
+
- CHANGELOG.md
|
36
|
+
- LICENSE.txt
|
37
|
+
- README.md
|
38
|
+
- doc/devices.md
|
39
|
+
- doc/lighting.md
|
40
|
+
- doc/power.md
|
41
|
+
- exe/wizrb
|
42
|
+
- lib/wizrb.rb
|
43
|
+
- lib/wizrb/cli.rb
|
44
|
+
- lib/wizrb/lighting.rb
|
45
|
+
- lib/wizrb/lighting/discover.rb
|
46
|
+
- lib/wizrb/lighting/events/set_brightness_event.rb
|
47
|
+
- lib/wizrb/lighting/events/set_cold_white_event.rb
|
48
|
+
- lib/wizrb/lighting/events/set_color_temp_event.rb
|
49
|
+
- lib/wizrb/lighting/events/set_rgb_event.rb
|
50
|
+
- lib/wizrb/lighting/events/set_scene_event.rb
|
51
|
+
- lib/wizrb/lighting/events/set_speed_event.rb
|
52
|
+
- lib/wizrb/lighting/events/set_warm_white_event.rb
|
53
|
+
- lib/wizrb/lighting/group.rb
|
54
|
+
- lib/wizrb/lighting/products/dimable_light.rb
|
55
|
+
- lib/wizrb/lighting/products/light.rb
|
56
|
+
- lib/wizrb/lighting/products/rgb_light.rb
|
57
|
+
- lib/wizrb/lighting/products/tunable_light.rb
|
58
|
+
- lib/wizrb/lighting/scenes/scene.rb
|
59
|
+
- lib/wizrb/lighting/scenes/spooky_scene.rb
|
60
|
+
- lib/wizrb/lighting/state.rb
|
61
|
+
- lib/wizrb/power/discover.rb
|
62
|
+
- lib/wizrb/power/group.rb
|
63
|
+
- lib/wizrb/power/products/smart_plug.rb
|
64
|
+
- lib/wizrb/shared/connection.rb
|
65
|
+
- lib/wizrb/shared/discover.rb
|
66
|
+
- lib/wizrb/shared/events/base.rb
|
67
|
+
- lib/wizrb/shared/events/power_event.rb
|
68
|
+
- lib/wizrb/shared/events/reboot_event.rb
|
69
|
+
- lib/wizrb/shared/events/refresh_event.rb
|
70
|
+
- lib/wizrb/shared/events/reset_event.rb
|
71
|
+
- lib/wizrb/shared/group.rb
|
72
|
+
- lib/wizrb/shared/products/device.rb
|
73
|
+
- lib/wizrb/shared/state.rb
|
74
|
+
- lib/wizrb/version.rb
|
34
75
|
homepage: https://github.com/bert-mccutchen/wizrb
|
35
76
|
licenses:
|
36
77
|
- MIT
|