wizrb 0.1.0 → 1.1.2

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 (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 +50 -9
@@ -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: 0.1.0
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bert McCutchen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-29 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
@@ -38,7 +79,7 @@ metadata:
38
79
  homepage_uri: https://github.com/bert-mccutchen/wizrb
39
80
  source_code_uri: https://github.com/bert-mccutchen/wizrb
40
81
  changelog_uri: https://github.com/bert-mccutchen/wizrb/blob/master/CHANGELOG.md
41
- post_install_message:
82
+ post_install_message:
42
83
  rdoc_options: []
43
84
  require_paths:
44
85
  - lib
@@ -46,15 +87,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
87
  requirements:
47
88
  - - ">="
48
89
  - !ruby/object:Gem::Version
49
- version: 3.0.0
90
+ version: 2.5.0
50
91
  required_rubygems_version: !ruby/object:Gem::Requirement
51
92
  requirements:
52
93
  - - ">="
53
94
  - !ruby/object:Gem::Version
54
95
  version: '0'
55
96
  requirements: []
56
- rubygems_version: 3.2.22
57
- signing_key:
97
+ rubygems_version: 3.0.3.1
98
+ signing_key:
58
99
  specification_version: 4
59
100
  summary: State management for Philips WiZ devices.
60
101
  test_files: []