wizrb 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +62 -0
  5. data/doc/devices.md +135 -0
  6. data/doc/lighting.md +186 -0
  7. data/doc/power.md +37 -0
  8. data/exe/wizrb +5 -0
  9. data/lib/wizrb/cli.rb +79 -0
  10. data/lib/wizrb/lighting/discover.rb +40 -0
  11. data/lib/wizrb/lighting/events/set_brightness_event.rb +27 -0
  12. data/lib/wizrb/lighting/events/set_cold_white_event.rb +27 -0
  13. data/lib/wizrb/lighting/events/set_color_temp_event.rb +29 -0
  14. data/lib/wizrb/lighting/events/set_rgb_event.rb +29 -0
  15. data/lib/wizrb/lighting/events/set_scene_event.rb +22 -0
  16. data/lib/wizrb/lighting/events/set_speed_event.rb +27 -0
  17. data/lib/wizrb/lighting/events/set_warm_white_event.rb +27 -0
  18. data/lib/wizrb/lighting/group.rb +37 -0
  19. data/lib/wizrb/lighting/products/dimable_light.rb +33 -0
  20. data/lib/wizrb/lighting/products/light.rb +55 -0
  21. data/lib/wizrb/lighting/products/rgb_light.rb +58 -0
  22. data/lib/wizrb/lighting/products/tunable_light.rb +39 -0
  23. data/lib/wizrb/lighting/scenes/scene.rb +68 -0
  24. data/lib/wizrb/lighting/scenes/spooky_scene.rb +61 -0
  25. data/lib/wizrb/lighting/state.rb +45 -0
  26. data/lib/wizrb/lighting.rb +41 -0
  27. data/lib/wizrb/power/discover.rb +34 -0
  28. data/lib/wizrb/power/group.rb +10 -0
  29. data/lib/wizrb/power/products/smart_plug.rb +17 -0
  30. data/lib/wizrb/shared/connection.rb +76 -0
  31. data/lib/wizrb/shared/discover.rb +116 -0
  32. data/lib/wizrb/shared/events/base.rb +22 -0
  33. data/lib/wizrb/shared/events/power_event.rb +22 -0
  34. data/lib/wizrb/shared/events/reboot_event.rb +15 -0
  35. data/lib/wizrb/shared/events/refresh_event.rb +15 -0
  36. data/lib/wizrb/shared/events/reset_event.rb +15 -0
  37. data/lib/wizrb/shared/group.rb +68 -0
  38. data/lib/wizrb/shared/products/device.rb +105 -0
  39. data/lib/wizrb/shared/state.rb +32 -0
  40. data/lib/wizrb/version.rb +5 -0
  41. data/lib/wizrb.rb +32 -0
  42. 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Wizrb
6
+ module Shared
7
+ module Events
8
+ class RebootEvent < Wizrb::Shared::Events::Base
9
+ def initialize
10
+ super(method: 'reboot')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Wizrb
6
+ module Shared
7
+ module Events
8
+ class RefreshEvent < Wizrb::Shared::Events::Base
9
+ def initialize
10
+ super(method: 'getPilot')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module Wizrb
6
+ module Shared
7
+ module Events
8
+ class ResetEvent < Wizrb::Shared::Events::Base
9
+ def initialize
10
+ super(method: 'reset')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wizrb
4
+ VERSION = '1.1.2'
5
+ 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.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-05 00:00:00.000000000 Z
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