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.
- 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 +50 -9
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../shared/events/base'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Events
|
8
|
+
class SetRgbEvent < Wizrb::Shared::Events::Base
|
9
|
+
MIN_VALUE = 1
|
10
|
+
MAX_VALUE = 255
|
11
|
+
|
12
|
+
def initialize(red, green, blue)
|
13
|
+
validate_color!('Red', red)
|
14
|
+
validate_color!('Green', green)
|
15
|
+
validate_color!('Blue', blue)
|
16
|
+
super(method: 'setState', params: { r: red, g: green, b: blue })
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_color!(color, value)
|
22
|
+
return if value && value >= MIN_VALUE && value <= MAX_VALUE
|
23
|
+
|
24
|
+
raise ArgumentError, "#{color} must be between #{MIN_VALUE} and #{MAX_VALUE}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../shared/events/base'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Events
|
8
|
+
class SetSceneEvent < Wizrb::Shared::Events::Base
|
9
|
+
def initialize(value)
|
10
|
+
validate!(value)
|
11
|
+
super(method: 'setState', params: { sceneId: Wizrb::Lighting::SCENES[value] })
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate!(value)
|
17
|
+
raise ArgumentError, 'Invalid scene' unless Wizrb::Lighting::SCENES.include?(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../shared/events/base'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Events
|
8
|
+
class SetSpeedEvent < Wizrb::Shared::Events::Base
|
9
|
+
MIN_VALUE = 10
|
10
|
+
MAX_VALUE = 200
|
11
|
+
|
12
|
+
def initialize(value)
|
13
|
+
validate!(value)
|
14
|
+
super(method: 'setState', params: { speed: value })
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate!(value)
|
20
|
+
return if value && value >= MIN_VALUE && value <= MAX_VALUE
|
21
|
+
|
22
|
+
raise ArgumentError, "Speed must be between #{MIN_VALUE} and #{MAX_VALUE}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../shared/events/base'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Events
|
8
|
+
class SetWarmWhiteEvent < Wizrb::Shared::Events::Base
|
9
|
+
MIN_VALUE = 1
|
10
|
+
MAX_VALUE = 255
|
11
|
+
|
12
|
+
def initialize(value)
|
13
|
+
validate!(value)
|
14
|
+
super(method: 'setState', params: { w: value })
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate!(value)
|
20
|
+
return if value && value >= MIN_VALUE && value <= MAX_VALUE
|
21
|
+
|
22
|
+
raise ArgumentError, "Warm white must be between #{MIN_VALUE} and #{MAX_VALUE}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../shared/group'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
class Group < Wizrb::Shared::Group
|
8
|
+
def brightness(value)
|
9
|
+
dispatch_event(Wizrb::Lighting::Events::SetBrightnessEvent.new(value))
|
10
|
+
end
|
11
|
+
|
12
|
+
def cold_white(value)
|
13
|
+
dispatch_event(Wizrb::Lighting::Events::SetColdWhiteEvent.new(value))
|
14
|
+
end
|
15
|
+
|
16
|
+
def color_temp(value)
|
17
|
+
dispatch_event(Wizrb::Lighting::Events::SetColorTempEvent.new(value))
|
18
|
+
end
|
19
|
+
|
20
|
+
def rgb(red, green, blue)
|
21
|
+
dispatch_event(Wizrb::Lighting::Events::SetRgbEvent.new(red, green, blue))
|
22
|
+
end
|
23
|
+
|
24
|
+
def speed(value)
|
25
|
+
dispatch_event(Wizrb::Lighting::Events::SetSpeedEvent.new(value))
|
26
|
+
end
|
27
|
+
|
28
|
+
def warm_white(value)
|
29
|
+
dispatch_event(Wizrb::Lighting::Events::SetWarmWhiteEvent.new(value))
|
30
|
+
end
|
31
|
+
|
32
|
+
def scene(value)
|
33
|
+
dispatch_event(Wizrb::Lighting::Events::SetSceneEvent.new(value))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'light'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Products
|
8
|
+
class DimableLight < Wizrb::Lighting::Products::Light
|
9
|
+
MODULE_NAME_IDENTIFIER = 'DW'
|
10
|
+
FEATURES = {
|
11
|
+
brightness: true,
|
12
|
+
color_temp: false,
|
13
|
+
color: false,
|
14
|
+
effect: false,
|
15
|
+
scenes: %i[
|
16
|
+
wake_up
|
17
|
+
bedtime
|
18
|
+
cool_white
|
19
|
+
night_light
|
20
|
+
candlelight
|
21
|
+
golden_white
|
22
|
+
pulse
|
23
|
+
steampunk
|
24
|
+
]
|
25
|
+
}.freeze
|
26
|
+
|
27
|
+
def initialize(ip:, port: 38_899)
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
require_relative '../../shared/products/device'
|
6
|
+
require_relative '../state'
|
7
|
+
|
8
|
+
module Wizrb
|
9
|
+
module Lighting
|
10
|
+
module Products
|
11
|
+
class Light < Wizrb::Shared::Products::Device
|
12
|
+
def initialize(ip:, port: 38_899)
|
13
|
+
super(ip: ip, port: port, state: Wizrb::Lighting::State.new)
|
14
|
+
end
|
15
|
+
|
16
|
+
def white_range
|
17
|
+
@white_range ||=
|
18
|
+
user_config&.dig('whiteRange') ||
|
19
|
+
model_config&.dig('extRange') ||
|
20
|
+
model_config&.dig('cctRange') ||
|
21
|
+
user_config&.dig('extRange') ||
|
22
|
+
user_config&.dig('cctRange')
|
23
|
+
end
|
24
|
+
|
25
|
+
def brightness(value)
|
26
|
+
dispatch_event(Wizrb::Lighting::Events::SetBrightnessEvent.new(value))
|
27
|
+
end
|
28
|
+
|
29
|
+
def cold_white(value)
|
30
|
+
dispatch_event(Wizrb::Lighting::Events::SetColdWhiteEvent.new(value))
|
31
|
+
end
|
32
|
+
|
33
|
+
def color_temp(value)
|
34
|
+
dispatch_event(Wizrb::Lighting::Events::SetColorTempEvent.new(value))
|
35
|
+
end
|
36
|
+
|
37
|
+
def rgb(red, green, blue)
|
38
|
+
dispatch_event(Wizrb::Lighting::Events::SetRgbEvent.new(red, green, blue))
|
39
|
+
end
|
40
|
+
|
41
|
+
def speed(value)
|
42
|
+
dispatch_event(Wizrb::Lighting::Events::SetSpeedEvent.new(value))
|
43
|
+
end
|
44
|
+
|
45
|
+
def warm_white(value)
|
46
|
+
dispatch_event(Wizrb::Lighting::Events::SetWarmWhiteEvent.new(value))
|
47
|
+
end
|
48
|
+
|
49
|
+
def scene(value)
|
50
|
+
dispatch_event(Wizrb::Lighting::Events::SetSceneEvent.new(value))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'light'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Products
|
8
|
+
class RgbLight < Wizrb::Lighting::Products::Light
|
9
|
+
MODULE_NAME_IDENTIFIER = 'RGB'
|
10
|
+
FEATURES = {
|
11
|
+
brightness: true,
|
12
|
+
color_temp: true,
|
13
|
+
color: true,
|
14
|
+
effect: true,
|
15
|
+
scenes: %i[
|
16
|
+
ocean
|
17
|
+
romance
|
18
|
+
sunset
|
19
|
+
party
|
20
|
+
fireplace
|
21
|
+
cozy
|
22
|
+
forest
|
23
|
+
pastel_colors
|
24
|
+
wake_up
|
25
|
+
bedtime
|
26
|
+
warm_white
|
27
|
+
daylight
|
28
|
+
cool_white
|
29
|
+
night_light
|
30
|
+
focus
|
31
|
+
relax
|
32
|
+
true_colors
|
33
|
+
tv_time
|
34
|
+
plantgrowth
|
35
|
+
spring
|
36
|
+
summer
|
37
|
+
fall
|
38
|
+
deepdive
|
39
|
+
jungle
|
40
|
+
mojito
|
41
|
+
club
|
42
|
+
christmas
|
43
|
+
halloween
|
44
|
+
candlelight
|
45
|
+
golden_white
|
46
|
+
pulse
|
47
|
+
steampunk
|
48
|
+
rhythm
|
49
|
+
]
|
50
|
+
}.freeze
|
51
|
+
|
52
|
+
def initialize(ip:, port: 38_899)
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'light'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Products
|
8
|
+
class TunableLight < Wizrb::Lighting::Products::Light
|
9
|
+
MODULE_NAME_IDENTIFIER = 'TW'
|
10
|
+
FEATURES = {
|
11
|
+
brightness: true,
|
12
|
+
color_temp: true,
|
13
|
+
color: false,
|
14
|
+
effect: true,
|
15
|
+
scenes: %i[
|
16
|
+
cozy
|
17
|
+
wake_up
|
18
|
+
bedtime
|
19
|
+
warm_white
|
20
|
+
daylight
|
21
|
+
cool_white
|
22
|
+
night_light
|
23
|
+
focus
|
24
|
+
relax
|
25
|
+
tv_time
|
26
|
+
candlelight
|
27
|
+
golden_white
|
28
|
+
pulse
|
29
|
+
steampunk
|
30
|
+
]
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def initialize(ip:, port: 38_899)
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../group'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Scenes
|
8
|
+
class Scene
|
9
|
+
def initialize(group, stop_delay: 1, save: true)
|
10
|
+
validate!(group)
|
11
|
+
@group = group
|
12
|
+
@stop_delay = stop_delay
|
13
|
+
@save = save
|
14
|
+
@running = false
|
15
|
+
@thread = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
@running = true
|
20
|
+
@thread = Thread.new do
|
21
|
+
save_state if @save
|
22
|
+
before_start
|
23
|
+
step while @running
|
24
|
+
after_stop
|
25
|
+
ensure
|
26
|
+
restore_state if @save
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@running = false
|
32
|
+
sleep(@stop_delay)
|
33
|
+
@thread.terminate
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def before_start; end
|
39
|
+
|
40
|
+
def step
|
41
|
+
raise Wizrb::Error, 'You failed to implement #step on your scene'
|
42
|
+
end
|
43
|
+
|
44
|
+
def after_stop; end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate!(value)
|
49
|
+
return if value.is_a?(Wizrb::Lighting::Group)
|
50
|
+
|
51
|
+
raise Wizrb::Error, 'Scenes can only be activated on lighting groups'
|
52
|
+
end
|
53
|
+
|
54
|
+
def save_state
|
55
|
+
@state_events = @group.map do |device|
|
56
|
+
Wizrb::Shared::Events::Base.new(method: 'setState', params: device.refresh.state)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def restore_state
|
61
|
+
@group.each_with_index do |device, i|
|
62
|
+
device.dispatch_event(@state_events[i])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'scene'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Lighting
|
7
|
+
module Scenes
|
8
|
+
class SpookyScene < Wizrb::Lighting::Scenes::Scene
|
9
|
+
DEFAULT_STATE_EVENTS = [
|
10
|
+
Wizrb::Shared::Events::PowerEvent.new(true),
|
11
|
+
Wizrb::Lighting::Events::SetSpeedEvent.new(200),
|
12
|
+
Wizrb::Lighting::Events::SetColorTempEvent.new(1000),
|
13
|
+
Wizrb::Lighting::Events::SetBrightnessEvent.new(100)
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
BLACKOUT_EVENTS = [
|
17
|
+
Wizrb::Shared::Events::PowerEvent.new(false)
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
def initialize(group, sync: false)
|
21
|
+
@sync = sync
|
22
|
+
super(group, stop_delay: 10)
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def before_start
|
28
|
+
dispatch(DEFAULT_STATE_EVENTS, sync: true)
|
29
|
+
end
|
30
|
+
|
31
|
+
def step
|
32
|
+
if Random.rand > 0.5
|
33
|
+
Random.rand(1..10).times do
|
34
|
+
next if Random.rand > 0.25
|
35
|
+
|
36
|
+
dispatch(BLACKOUT_EVENTS, sync: @sync)
|
37
|
+
sleep(Random.rand > 0.98 ? 3 : 0.125)
|
38
|
+
dispatch(DEFAULT_STATE_EVENTS, sync: true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
sleep(Random.rand(0.125..5.0))
|
43
|
+
end
|
44
|
+
|
45
|
+
def after_stop
|
46
|
+
dispatch(DEFAULT_STATE_EVENTS, sync: true)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def dispatch(events, sync: false)
|
52
|
+
if sync || Random.rand > 0.5
|
53
|
+
@group.dispatch_events(*events)
|
54
|
+
else
|
55
|
+
@group.devices.sample.dispatch_events(*events)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wizrb
|
4
|
+
module Lighting
|
5
|
+
class State < Wizrb::Shared::State
|
6
|
+
STATE_KEYS = %i[state w c r g b temp dimming speed sceneId].freeze
|
7
|
+
|
8
|
+
def parse!(response)
|
9
|
+
result = response&.dig('result')
|
10
|
+
return unless result
|
11
|
+
|
12
|
+
@state = result.transform_keys(&:to_sym).slice(*STATE_KEYS)
|
13
|
+
@state.delete(:sceneId) if @state[:sceneId]&.zero?
|
14
|
+
end
|
15
|
+
|
16
|
+
def warm_white
|
17
|
+
@state[:w]
|
18
|
+
end
|
19
|
+
|
20
|
+
def cold_white
|
21
|
+
@state[:c]
|
22
|
+
end
|
23
|
+
|
24
|
+
def rgb
|
25
|
+
{ red: @state[:r], green: @state[:g], blue: @state[:b] }
|
26
|
+
end
|
27
|
+
|
28
|
+
def color_temp
|
29
|
+
@state[:temp]
|
30
|
+
end
|
31
|
+
|
32
|
+
def brightness
|
33
|
+
@state[:dimming]
|
34
|
+
end
|
35
|
+
|
36
|
+
def speed
|
37
|
+
@state[:speed]
|
38
|
+
end
|
39
|
+
|
40
|
+
def scene
|
41
|
+
Wizrb::Lighting::SCENES.key(@state[:schdPsetId] || @state[:sceneId])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Wizrb
|
4
|
+
module Lighting
|
5
|
+
SCENES = {
|
6
|
+
ocean: 1,
|
7
|
+
romance: 2,
|
8
|
+
sunset: 3,
|
9
|
+
party: 4,
|
10
|
+
fireplace: 5,
|
11
|
+
cozy: 6,
|
12
|
+
forest: 7,
|
13
|
+
pastel_colors: 8,
|
14
|
+
wake_up: 9,
|
15
|
+
bedtime: 10,
|
16
|
+
warm_white: 11,
|
17
|
+
daylight: 12,
|
18
|
+
cool_white: 13,
|
19
|
+
night_light: 14,
|
20
|
+
focus: 15,
|
21
|
+
relax: 16,
|
22
|
+
true_colors: 17,
|
23
|
+
tv_time: 18,
|
24
|
+
plantgrowth: 19,
|
25
|
+
spring: 20,
|
26
|
+
summer: 21,
|
27
|
+
fall: 22,
|
28
|
+
deepdive: 23,
|
29
|
+
jungle: 24,
|
30
|
+
mojito: 25,
|
31
|
+
club: 26,
|
32
|
+
christmas: 27,
|
33
|
+
halloween: 28,
|
34
|
+
candlelight: 29,
|
35
|
+
golden_white: 30,
|
36
|
+
pulse: 31,
|
37
|
+
steampunk: 32,
|
38
|
+
rhythm: 1000
|
39
|
+
}.freeze
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../shared/discover'
|
4
|
+
require_relative 'group'
|
5
|
+
require_relative 'products/smart_plug'
|
6
|
+
|
7
|
+
module Wizrb
|
8
|
+
module Power
|
9
|
+
class Discover < Wizrb::Shared::Discover
|
10
|
+
private
|
11
|
+
|
12
|
+
def parse_response(data, addr)
|
13
|
+
response = JSON.parse(data)
|
14
|
+
return unless response.dig('result', 'success') && addr[1] && addr[2]
|
15
|
+
|
16
|
+
resolve_device(ip: addr[2], port: addr[1])
|
17
|
+
rescue StandardError
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve_device(ip:, port: 38_899)
|
22
|
+
module_name = Wizrb::Shared::Products::Device.new(ip: ip, port: port).module_name
|
23
|
+
|
24
|
+
return unless module_name.include?(Wizrb::Power::Products::SmartPlug::MODULE_NAME_IDENTIFIER)
|
25
|
+
|
26
|
+
Wizrb::Power::Products::SmartPlug.new(ip: ip, port: port)
|
27
|
+
end
|
28
|
+
|
29
|
+
def group_devices
|
30
|
+
Wizrb::Power::Group.new(devices: @devices)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../shared/products/device'
|
4
|
+
|
5
|
+
module Wizrb
|
6
|
+
module Power
|
7
|
+
module Products
|
8
|
+
class SmartPlug < Wizrb::Shared::Products::Device
|
9
|
+
MODULE_NAME_IDENTIFIER = 'SOCKET'
|
10
|
+
|
11
|
+
def initialize(ip:, port: 38_899)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
module Wizrb
|
7
|
+
module Shared
|
8
|
+
class Connection
|
9
|
+
attr_reader :ip, :port, :connection_id, :socket
|
10
|
+
|
11
|
+
def initialize(ip, port)
|
12
|
+
@ip = ip
|
13
|
+
@port = port
|
14
|
+
@connection_id = Time.now.to_i.to_s(16)
|
15
|
+
@socket = UDPSocket.new
|
16
|
+
log('Created')
|
17
|
+
end
|
18
|
+
|
19
|
+
def connect
|
20
|
+
with_error_logging do
|
21
|
+
socket.connect(ip, port)
|
22
|
+
log('Connected')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def send(data)
|
27
|
+
with_error_logging do
|
28
|
+
connect
|
29
|
+
log("Sending: #{data.to_json}")
|
30
|
+
socket.send(data.to_json.encode('UTF-8'), 0)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive(timeout: 2, max: 1024)
|
35
|
+
with_error_logging do
|
36
|
+
connect
|
37
|
+
|
38
|
+
ready = socket.wait_readable(timeout)
|
39
|
+
raise Wizrb::ConnectionTimeoutError unless ready
|
40
|
+
|
41
|
+
data, _addr = socket.recvfrom(max)
|
42
|
+
log("Received: #{data}")
|
43
|
+
parse_response(data)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test
|
48
|
+
with_error_logging do
|
49
|
+
send({ method: 'getPilot', params: {} })
|
50
|
+
receive
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def parse_response(data)
|
57
|
+
response = JSON.parse(data)
|
58
|
+
|
59
|
+
raise Wizrb::ConnectionError, response['error'] if response.key?('error')
|
60
|
+
|
61
|
+
response
|
62
|
+
end
|
63
|
+
|
64
|
+
def log(message)
|
65
|
+
puts "[Wizrb::Connection##{connection_id} #{ip}:#{port}] #{message}" if ENV['DEBUG']
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_error_logging
|
69
|
+
yield
|
70
|
+
rescue StandardError => e
|
71
|
+
log("Error: #{e.message}")
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|