esphome 1.0.0 → 1.1.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/exe/esphome-monitor +4 -12
- data/exe/esphome-serial-proxy +132 -0
- data/lib/esphome/action.rb +55 -0
- data/lib/esphome/api/README.md +3 -0
- data/lib/esphome/api/api.proto +2756 -0
- data/lib/esphome/api/api_options.proto +120 -0
- data/lib/esphome/api/api_options_pb.rb +20 -0
- data/lib/esphome/api/api_pb.rb +229 -0
- data/lib/esphome/api.rb +182 -0
- data/lib/esphome/cli/colors.rb +44 -0
- data/lib/esphome/cli/completion.rb +48 -0
- data/lib/esphome/cli/entities/button.rb +18 -0
- data/lib/esphome/cli/entities/climate.rb +196 -0
- data/lib/esphome/cli/entities/cover.rb +116 -0
- data/lib/esphome/cli/entities/date.rb +27 -0
- data/lib/esphome/cli/entities/date_time.rb +29 -0
- data/lib/esphome/cli/entities/form.rb +111 -0
- data/lib/esphome/cli/entities/lock.rb +20 -0
- data/lib/esphome/cli/entities/menu.rb +83 -0
- data/lib/esphome/cli/entities/number.rb +37 -0
- data/lib/esphome/cli/entities/select.rb +16 -0
- data/lib/esphome/cli/entities/subfields.rb +63 -0
- data/lib/esphome/cli/entities/switch.rb +18 -0
- data/lib/esphome/cli/entities/text.rb +16 -0
- data/lib/esphome/cli/entities/time.rb +40 -0
- data/lib/esphome/cli/entity.rb +78 -0
- data/lib/esphome/cli/monitor.rb +441 -0
- data/lib/esphome/dashboard.rb +86 -0
- data/lib/esphome/device.rb +416 -0
- data/lib/esphome/entities/binary_sensor.rb +66 -0
- data/lib/esphome/entities/button.rb +13 -0
- data/lib/esphome/entities/climate.rb +259 -0
- data/lib/esphome/entities/cover.rb +135 -0
- data/lib/esphome/entities/date.rb +23 -0
- data/lib/esphome/entities/date_time.rb +21 -0
- data/lib/esphome/entities/fan.rb +89 -0
- data/lib/esphome/entities/light.rb +158 -0
- data/lib/esphome/entities/lock.rb +53 -0
- data/lib/esphome/entities/number.rb +56 -0
- data/lib/esphome/entities/select.rb +27 -0
- data/lib/esphome/entities/sensor.rb +49 -0
- data/lib/esphome/entities/switch.rb +24 -0
- data/lib/esphome/entities/text.rb +36 -0
- data/lib/esphome/entities/text_sensor.rb +10 -0
- data/lib/esphome/entities/time.rb +39 -0
- data/lib/esphome/entity.rb +146 -0
- data/lib/esphome/error.rb +28 -0
- data/lib/esphome/serial_proxy.rb +372 -0
- data/lib/esphome/version.rb +5 -0
- data/lib/esphome.rb +5 -0
- data/lib/httpx/plugins/websocket.rb +62 -0
- data/lib/websocket/driver/httpx.rb +74 -0
- metadata +54 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Lock < Entity
|
|
6
|
+
include HasAssumedState
|
|
7
|
+
include HasState
|
|
8
|
+
|
|
9
|
+
def initialize(_device, list_entities_response)
|
|
10
|
+
super
|
|
11
|
+
@supports_open = list_entities_response.supports_open
|
|
12
|
+
@requires_code = list_entities_response.requires_code
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def supports_open?
|
|
16
|
+
@supports_open
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def requires_code?
|
|
20
|
+
@requires_code
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def update(state_response)
|
|
24
|
+
@state = (state_response.state == :LOCK_STATE_NONE) ? nil : state_response.state[11..].downcase.to_sym
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def command(command, code = nil)
|
|
28
|
+
message = Api::LockCommandRequest.new(key:, command: :"LOCK_#{command.to_s.upcase}")
|
|
29
|
+
if code
|
|
30
|
+
message.code = code
|
|
31
|
+
message.has_code = true
|
|
32
|
+
end
|
|
33
|
+
device.send(message)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lock(code = nil) = command(:lock, code)
|
|
37
|
+
def unlock(code = nil) = command(:unlock, code)
|
|
38
|
+
def open(code = nil) = command(:open, code)
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def inspection_vars
|
|
43
|
+
super + %i[supports_open? requires_code?]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def hideable?(var, val)
|
|
47
|
+
super ||
|
|
48
|
+
(var == :supports_open && val) ||
|
|
49
|
+
(var == :requires_code && val)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Number < Entity
|
|
6
|
+
include HasDeviceClass
|
|
7
|
+
include HasState
|
|
8
|
+
|
|
9
|
+
attr_reader :range, :step, :unit_of_measurement, :mode
|
|
10
|
+
|
|
11
|
+
def initialize(_device, list_entities_response)
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
@range = Range.new(list_entities_response.min_value, list_entities_response.max_value)
|
|
15
|
+
@max_value = list_entities_response.max_value
|
|
16
|
+
@step = list_entities_response.step
|
|
17
|
+
@unit_of_measurement = list_entities_response.unit_of_measurement
|
|
18
|
+
@mode = list_entities_response.mode[12..].downcase.to_sym
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def accuracy_decimals
|
|
22
|
+
str = step.to_s
|
|
23
|
+
return 0 unless str.include?(".")
|
|
24
|
+
|
|
25
|
+
decimals = str.split(".").last
|
|
26
|
+
return 0 if decimals == "0"
|
|
27
|
+
|
|
28
|
+
decimals.size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def formatted_state(state = self.state)
|
|
32
|
+
result = if state
|
|
33
|
+
format("%.#{accuracy_decimals}f", state)
|
|
34
|
+
else
|
|
35
|
+
"-"
|
|
36
|
+
end
|
|
37
|
+
result += " #{unit_of_measurement}" if unit_of_measurement
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set(state)
|
|
42
|
+
device.send(Api::NumberCommandRequest.new(key:, state:))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def inspection_vars
|
|
48
|
+
super + %i[range step unit_of_measurement mode]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def hideable?(var, val)
|
|
52
|
+
super || (var == :mode && val == :auto)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Select < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
attr_reader :options
|
|
9
|
+
|
|
10
|
+
def initialize(_device, list_entities_response)
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
@options = list_entities_response.options.map(&:freeze).freeze
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def command(state)
|
|
17
|
+
device.send(Api::SelectCommandRequest.new(key:, state:))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def inspection_vars
|
|
23
|
+
super + [:options]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Sensor < Entity
|
|
6
|
+
include HasDeviceClass
|
|
7
|
+
include HasState
|
|
8
|
+
|
|
9
|
+
attr_reader :unit_of_measurement,
|
|
10
|
+
:accuracy_decimals,
|
|
11
|
+
:state_class
|
|
12
|
+
|
|
13
|
+
def initialize(_device, list_entities_response)
|
|
14
|
+
super
|
|
15
|
+
|
|
16
|
+
@unit_of_measurement = list_entities_response.unit_of_measurement
|
|
17
|
+
@accuracy_decimals = list_entities_response.accuracy_decimals
|
|
18
|
+
@force_update = list_entities_response.force_update
|
|
19
|
+
@state_class = list_entities_response.state_class[12..].downcase.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def force_update?
|
|
23
|
+
@force_update
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def formatted_state
|
|
27
|
+
result = if state
|
|
28
|
+
format("%.#{accuracy_decimals}f", state)
|
|
29
|
+
else
|
|
30
|
+
"-"
|
|
31
|
+
end
|
|
32
|
+
result += " #{unit_of_measurement}" if unit_of_measurement
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def inspection_vars
|
|
39
|
+
super + %i[unit_of_measurement accuracy_decimals force_update? state_class]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def hideable?(var, val)
|
|
43
|
+
super ||
|
|
44
|
+
(var == :force_update? && !val) ||
|
|
45
|
+
(var == :state_class && val == :none)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Switch < Entity
|
|
6
|
+
include HasDeviceClass
|
|
7
|
+
include HasAssumedState
|
|
8
|
+
include HasState
|
|
9
|
+
|
|
10
|
+
def update(state_response)
|
|
11
|
+
@state = state_response.state
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def command(state)
|
|
15
|
+
device.send(Api::SwitchCommandRequest.new(key:, state:))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def formatted_state = state ? "on" : "off"
|
|
19
|
+
|
|
20
|
+
def on = command(true)
|
|
21
|
+
def off = command(false)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Text < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
attr_reader :length_range, :pattern, :mode
|
|
9
|
+
|
|
10
|
+
def initialize(_device, list_entities_response)
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
@length_range = Range.new(list_entities_response.min_length, list_entities_response.max_length)
|
|
14
|
+
@pattern = list_entities_response.pattern.empty? ? nil : list_entities_response.pattern
|
|
15
|
+
@mode = list_entities_response.mode[10..].downcase.to_sym
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def command(state)
|
|
19
|
+
device.send(Api::TextCommandRequest.new(key:, state:))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def inspection_vars
|
|
25
|
+
super + %i[length_range pattern mode]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def hideable?(var, val)
|
|
29
|
+
super ||
|
|
30
|
+
(var == :length_range && length_range == (0..0)) ||
|
|
31
|
+
(var == :pattern && pattern.nil?) ||
|
|
32
|
+
(var == :mode && val == :text)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Time < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
def update(state_response)
|
|
9
|
+
@state = if state_response.missing_state
|
|
10
|
+
nil
|
|
11
|
+
elsif defined?(ActiveSupport::Duration)
|
|
12
|
+
state_response.hour.hours +
|
|
13
|
+
state_response.minute.minutes +
|
|
14
|
+
state_response.second.seconds
|
|
15
|
+
else
|
|
16
|
+
[state_response.hour, state_response.minute, state_response.second]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def formatted_state
|
|
21
|
+
return format("%02d:%02d:%02d", *state) if state.is_a?(Array)
|
|
22
|
+
|
|
23
|
+
super
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def command(state)
|
|
27
|
+
if defined?(ActiveSupport::Duration)
|
|
28
|
+
parts = state.parts
|
|
29
|
+
device.send(Api::TimeCommandRequest.new(key:,
|
|
30
|
+
hour: parts[:hours] || 0,
|
|
31
|
+
minute: parts[:minutes] || 0,
|
|
32
|
+
second: parts[:seconds] || 0))
|
|
33
|
+
else
|
|
34
|
+
device.send(Api::TimeCommandRequest.new(key:, hour: state[0], minute: state[1], second: state[2]))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
class Entity
|
|
5
|
+
module HasState
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
super
|
|
8
|
+
|
|
9
|
+
klass.attr_reader :state
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @!visibility private
|
|
13
|
+
def initialize(...)
|
|
14
|
+
super
|
|
15
|
+
@state = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update(state_response)
|
|
19
|
+
@state = state_response.missing_state ? nil : state_response.state
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def formatted_state
|
|
23
|
+
state.nil? ? "-" : state.to_s
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def inspection_vars
|
|
29
|
+
super + [:state]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module HasAssumedState
|
|
34
|
+
def self.included(klass)
|
|
35
|
+
super
|
|
36
|
+
|
|
37
|
+
klass.attr_reader :assumed_state
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @!visibility private
|
|
41
|
+
def initialize(_device, list_entities_response)
|
|
42
|
+
super
|
|
43
|
+
|
|
44
|
+
@assumed_state = list_entities_response.assumed_state
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def assumed_state?
|
|
48
|
+
@assumed_state
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def inspection_vars
|
|
54
|
+
super + [:assumed_state?]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def hideable?(var, val)
|
|
58
|
+
super || (var == :assumed_state? && !val)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module HasDeviceClass
|
|
63
|
+
def self.included(klass)
|
|
64
|
+
super
|
|
65
|
+
|
|
66
|
+
klass.attr_reader :device_class
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @!visibility private
|
|
70
|
+
def initialize(_device, list_entities_response)
|
|
71
|
+
super
|
|
72
|
+
|
|
73
|
+
@device_class = list_entities_response.device_class.empty? ? nil : list_entities_response.device_class
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def inspection_vars
|
|
79
|
+
super + [:device_class]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def hideable?(var, val)
|
|
83
|
+
super || (var == :device_class && val.nil?)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
attr_reader :device,
|
|
88
|
+
:object_id_,
|
|
89
|
+
:key,
|
|
90
|
+
:name,
|
|
91
|
+
:icon,
|
|
92
|
+
:disabled_by_default,
|
|
93
|
+
:entity_category
|
|
94
|
+
|
|
95
|
+
# @!visibility private
|
|
96
|
+
def initialize(device, list_entities_response)
|
|
97
|
+
@device = device
|
|
98
|
+
@object_id_ = list_entities_response.name.downcase.tr(" ", "_").gsub(/[^a-z0-9_-]/, "_")
|
|
99
|
+
@key = list_entities_response.key
|
|
100
|
+
@name = list_entities_response.name
|
|
101
|
+
@icon = list_entities_response.icon.empty? ? nil : list_entities_response.icon
|
|
102
|
+
@disabled_by_default = list_entities_response.disabled_by_default
|
|
103
|
+
@entity_category = list_entities_response.entity_category[16..].downcase.to_sym
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [String]
|
|
107
|
+
def inspect
|
|
108
|
+
vars = inspection_vars.filter_map do |var|
|
|
109
|
+
val = __send__(var)
|
|
110
|
+
next if hideable?(var, val)
|
|
111
|
+
|
|
112
|
+
"#{var}=#{val.inspect}"
|
|
113
|
+
end
|
|
114
|
+
"#<#{self.class.name} #{vars.join(", ")}>"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def inspection_vars
|
|
120
|
+
%i[object_id_ key name icon disabled_by_default entity_category]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def hideable?(var, val)
|
|
124
|
+
(var == :disabled_by_default && !val) ||
|
|
125
|
+
(var == :icon && val.nil?) ||
|
|
126
|
+
(var == :entity_category && val == :none)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
require_relative "entities/binary_sensor"
|
|
132
|
+
require_relative "entities/button"
|
|
133
|
+
require_relative "entities/climate"
|
|
134
|
+
require_relative "entities/cover"
|
|
135
|
+
require_relative "entities/date"
|
|
136
|
+
require_relative "entities/date_time"
|
|
137
|
+
require_relative "entities/fan"
|
|
138
|
+
require_relative "entities/light"
|
|
139
|
+
require_relative "entities/lock"
|
|
140
|
+
require_relative "entities/number"
|
|
141
|
+
require_relative "entities/select"
|
|
142
|
+
require_relative "entities/sensor"
|
|
143
|
+
require_relative "entities/switch"
|
|
144
|
+
require_relative "entities/text"
|
|
145
|
+
require_relative "entities/text_sensor"
|
|
146
|
+
require_relative "entities/time"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class DeviceError < Error; end
|
|
7
|
+
class NotConnectedError < Error; end
|
|
8
|
+
class DeviceConnectionError < DeviceError; end
|
|
9
|
+
class AuthenticationError < DeviceError; end
|
|
10
|
+
class InvalidPasswordError < AuthenticationError; end
|
|
11
|
+
class ConnectionClosedError < DeviceConnectionError; end
|
|
12
|
+
class ProtocolError < DeviceError; end
|
|
13
|
+
class PlaintextProtocolError < ProtocolError; end
|
|
14
|
+
class UnknownProtocolError < ProtocolError; end
|
|
15
|
+
class MessageError < DeviceError; end
|
|
16
|
+
class InvalidMessageLengthError < MessageError; end
|
|
17
|
+
class UnknownMessageError < MessageError; end
|
|
18
|
+
|
|
19
|
+
class DashboardError < Error; end
|
|
20
|
+
class NoSuchDeviceError < DashboardError; end
|
|
21
|
+
class MissingEncryptionKeyError < DashboardError; end
|
|
22
|
+
|
|
23
|
+
class SerialProxyError < Error; end
|
|
24
|
+
class NoSuchSerialProxyError < SerialProxyError; end
|
|
25
|
+
class MissingProxyError < SerialProxyError; end
|
|
26
|
+
class MissingDeviceError < SerialProxyError; end
|
|
27
|
+
class UnexpectedMessage < SerialProxyError; end
|
|
28
|
+
end
|