esphome 0.6.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 +16 -14
- 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 +55 -5
- data/exe/esphome-update-all +0 -66
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Climate < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
BASE_FAN_MODES = Api::ClimateCommandRequest.descriptor.lookup("fan_mode")
|
|
9
|
+
.subtype
|
|
10
|
+
.map { |value| value.name[12..].downcase.to_sym }
|
|
11
|
+
.freeze
|
|
12
|
+
private_constant :BASE_FAN_MODES
|
|
13
|
+
BASE_PRESETS = Api::ClimateCommandRequest.descriptor.lookup("preset")
|
|
14
|
+
.subtype
|
|
15
|
+
.map { |value, _number| value.to_s }
|
|
16
|
+
.reject { |value| value == "CLIMATE_PRESET_NONE" }
|
|
17
|
+
.map { |value| value[15..].downcase.to_sym }
|
|
18
|
+
.freeze
|
|
19
|
+
private_constant :BASE_PRESETS
|
|
20
|
+
UNIT_SYMBOLS = {
|
|
21
|
+
celsius: "°C",
|
|
22
|
+
fahrenheit: "°F",
|
|
23
|
+
kelvin: "K"
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
module Features
|
|
27
|
+
SUPPORTS_CURRENT_TEMPERATURE = 1 << 0
|
|
28
|
+
SUPPORTS_TWO_POINT_TARGET_TEMPERATURE = 1 << 1
|
|
29
|
+
REQUIRES_TWO_POINT_TARGET_TEMPERATURE = 1 << 2
|
|
30
|
+
SUPPORTS_CURRENT_HUMIDITY = 1 << 3
|
|
31
|
+
SUPPORTS_TARGET_HUMIDITY = 1 << 4
|
|
32
|
+
SUPPORTS_ACTION = 1 << 5
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :supported_modes,
|
|
36
|
+
:visual_temperature_range,
|
|
37
|
+
:visual_target_temperature_step,
|
|
38
|
+
:supported_fan_modes,
|
|
39
|
+
:supported_swing_modes,
|
|
40
|
+
:supported_presets,
|
|
41
|
+
:visual_humidity_range,
|
|
42
|
+
:current_temperature,
|
|
43
|
+
:target_temperature,
|
|
44
|
+
:target_temperature_low,
|
|
45
|
+
:target_temperature_high,
|
|
46
|
+
:action,
|
|
47
|
+
:fan_mode,
|
|
48
|
+
:swing_mode,
|
|
49
|
+
:preset,
|
|
50
|
+
:current_humidity,
|
|
51
|
+
:target_humidity,
|
|
52
|
+
:temperature_unit
|
|
53
|
+
|
|
54
|
+
def initialize(_device, list_entities_response)
|
|
55
|
+
super
|
|
56
|
+
|
|
57
|
+
if list_entities_response.feature_flags
|
|
58
|
+
@supports_current_temperature = list_entities_response.feature_flags.anybits?(Features::SUPPORTS_CURRENT_TEMPERATURE)
|
|
59
|
+
@supports_two_point_target_temperature = list_entities_response.feature_flags.anybits?(Features::SUPPORTS_TWO_POINT_TARGET_TEMPERATURE)
|
|
60
|
+
@supports_current_humidity = list_entities_response.feature_flags.anybits?(Features::SUPPORTS_CURRENT_HUMIDITY)
|
|
61
|
+
@supports_target_humidity = list_entities_response.feature_flags.anybits?(Features::SUPPORTS_TARGET_HUMIDITY)
|
|
62
|
+
@supports_action = list_entities_response.feature_flags.anybits?(Features::SUPPORTS_ACTION)
|
|
63
|
+
else
|
|
64
|
+
@supports_current_temperature = list_entities_response.supports_current_temperature
|
|
65
|
+
@supports_two_point_target_temperature = list_entities_response.supports_two_point_target_temperature
|
|
66
|
+
@supports_current_humidity = list_entities_response.supports_current_humidity
|
|
67
|
+
@supports_target_humidity = list_entities_response.supports_target_humidity
|
|
68
|
+
@supports_action = list_entities_response.supports_action
|
|
69
|
+
end
|
|
70
|
+
@supported_modes = list_entities_response.supported_modes.map { |m| m[13..].downcase.to_sym }.freeze
|
|
71
|
+
@visual_temperature_range = Range.new(list_entities_response.visual_min_temperature,
|
|
72
|
+
list_entities_response.visual_max_temperature)
|
|
73
|
+
@visual_target_temperature_step = list_entities_response.visual_target_temperature_step
|
|
74
|
+
@supported_fan_modes = list_entities_response.supported_fan_modes.map { |m| m[12..].downcase.to_sym }
|
|
75
|
+
@supported_swing_modes = list_entities_response.supported_swing_modes.map { |m| m[14..].downcase.to_sym }.freeze
|
|
76
|
+
@supported_fan_modes.concat(list_entities_response.supported_custom_fan_modes.map(&:to_sym))
|
|
77
|
+
@supported_fan_modes.freeze
|
|
78
|
+
@supported_presets = list_entities_response.supported_presets.map { |p| p[15..].downcase.to_sym }
|
|
79
|
+
if list_entities_response.legacy_supports_away && !@supported_presets.include?(:away)
|
|
80
|
+
@supported_presets.push(:away)
|
|
81
|
+
end
|
|
82
|
+
@supported_presets.concat(list_entities_response.supported_custom_presets.map(&:to_sym))
|
|
83
|
+
@supported_presets.freeze
|
|
84
|
+
@visual_humidity_range = Range.new(list_entities_response.visual_min_humidity,
|
|
85
|
+
list_entities_response.visual_max_humidity)
|
|
86
|
+
@temperature_unit = list_entities_response.temperature_unit&.[](17..)&.downcase&.to_sym || :celsius
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def current_temperature?
|
|
90
|
+
@supports_current_temperature
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def two_point_target_temperature?
|
|
94
|
+
@supports_two_point_target_temperature
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def action?
|
|
98
|
+
@supports_action
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def current_humidity?
|
|
102
|
+
@supports_current_humidity
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def target_humidity?
|
|
106
|
+
@supports_target_humidity
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def update(state_response)
|
|
110
|
+
@state = state_response.mode[13..].downcase.to_sym
|
|
111
|
+
@current_temperature = state_response.current_temperature if current_temperature?
|
|
112
|
+
if two_point_target_temperature?
|
|
113
|
+
@target_temperature_low = state_response.target_temperature_low
|
|
114
|
+
@target_temperature_high = state_response.target_temperature_high
|
|
115
|
+
else
|
|
116
|
+
@target_temperature = state_response.target_temperature
|
|
117
|
+
end
|
|
118
|
+
@action = state_response.action[15..].downcase.to_sym if action?
|
|
119
|
+
@fan_mode = if state_response.custom_fan_mode.empty?
|
|
120
|
+
state_response.fan_mode[12..].downcase.to_sym
|
|
121
|
+
else
|
|
122
|
+
state_response.custom_fan_mode.downcase.to_sym
|
|
123
|
+
end
|
|
124
|
+
@swing_mode = state_response.swing_mode[14..].downcase.to_sym
|
|
125
|
+
@preset = if !state_response.custom_preset.empty?
|
|
126
|
+
state_response.custom_preset.to_sym
|
|
127
|
+
elsif state_response.unused_legacy_away
|
|
128
|
+
:away
|
|
129
|
+
elsif state_response.preset == :CLIMATE_PRESET_NONE
|
|
130
|
+
nil
|
|
131
|
+
else
|
|
132
|
+
state_response.preset[15..].downcase.to_sym
|
|
133
|
+
end
|
|
134
|
+
@current_humidity = state_response.current_humidity if current_humidity?
|
|
135
|
+
@target_humidity = state_response.target_humidity if target_humidity?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def formatted_state
|
|
139
|
+
formatted_segments.join(" ")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def formatted_segments
|
|
143
|
+
segments = [state.nil? ? "-" : state.to_s]
|
|
144
|
+
segments << "(#{action || "-"})" if action?
|
|
145
|
+
segments << "#{format_temperature(current_temperature)} /" if current_temperature?
|
|
146
|
+
if two_point_target_temperature?
|
|
147
|
+
segments << format_temperature(target_temperature_low)
|
|
148
|
+
segments << "-"
|
|
149
|
+
segments << format_temperature(target_temperature_high)
|
|
150
|
+
else
|
|
151
|
+
segments << format_temperature(target_temperature)
|
|
152
|
+
end
|
|
153
|
+
segments << "fan: #{fan_mode || "-"}" unless supported_fan_modes.empty?
|
|
154
|
+
segments << "swing: #{swing_mode || "-"}" unless supported_swing_modes.empty?
|
|
155
|
+
segments << formatted_preset_segment unless supported_presets.empty?
|
|
156
|
+
segments << "#{current_humidity || "-"} %RH" if current_humidity?
|
|
157
|
+
segments << "/" if current_humidity? && target_humidity?
|
|
158
|
+
segments << "#{target_humidity || "-"} %RH" if target_humidity?
|
|
159
|
+
segments
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def formatted_fan_segment
|
|
163
|
+
"fan: #{fan_mode || "-"}"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def formatted_swing_segment
|
|
167
|
+
"swing: #{swing_mode || "-"}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def formatted_preset_segment
|
|
171
|
+
preset ? preset.to_s : "<preset>"
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def temperature_decimals
|
|
175
|
+
str = visual_target_temperature_step.to_s
|
|
176
|
+
return 0 unless str.include?(".")
|
|
177
|
+
|
|
178
|
+
str.split(".").last.sub(/0+\z/, "").length
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def format_temperature(value)
|
|
182
|
+
return "-" if value.nil?
|
|
183
|
+
|
|
184
|
+
format("%.#{temperature_decimals}f #{unit_symbol}", value)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def format_humidity(value)
|
|
188
|
+
return "-" if value.nil?
|
|
189
|
+
|
|
190
|
+
format("%.0f %%RH", value)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def unit_symbol
|
|
194
|
+
UNIT_SYMBOLS[temperature_unit]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def change_mode(mode)
|
|
198
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
199
|
+
has_mode: true,
|
|
200
|
+
mode: :"CLIMATE_MODE_#{mode.to_s.upcase}"))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def change_fan_mode(mode)
|
|
204
|
+
mode = mode.to_sym
|
|
205
|
+
if BASE_FAN_MODES.include?(mode)
|
|
206
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
207
|
+
has_fan_mode: true,
|
|
208
|
+
fan_mode: :"CLIMATE_FAN_#{mode.to_s.upcase}"))
|
|
209
|
+
else
|
|
210
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
211
|
+
has_custom_fan_mode: true,
|
|
212
|
+
custom_fan_mode: mode.to_s))
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def change_target_temperature(value = nil, low: nil, high: nil)
|
|
217
|
+
command = Api::ClimateCommandRequest.new(key:)
|
|
218
|
+
if low
|
|
219
|
+
command.has_target_temperature_low = true
|
|
220
|
+
command.target_temperature_low = low
|
|
221
|
+
end
|
|
222
|
+
if high
|
|
223
|
+
command.has_target_temperature_high = true
|
|
224
|
+
command.target_temperature_high = high
|
|
225
|
+
end
|
|
226
|
+
if value
|
|
227
|
+
command.has_target_temperature = true
|
|
228
|
+
command.target_temperature = value
|
|
229
|
+
end
|
|
230
|
+
device.send(command)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def change_swing_mode(mode)
|
|
234
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
235
|
+
has_swing_mode: true,
|
|
236
|
+
swing_mode: :"CLIMATE_SWING_#{mode.to_s.upcase}"))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def change_target_humidity(value)
|
|
240
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
241
|
+
has_target_humidity: true,
|
|
242
|
+
target_humidity: value))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def change_preset(value)
|
|
246
|
+
value = value.to_sym
|
|
247
|
+
if BASE_PRESETS.include?(value)
|
|
248
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
249
|
+
has_preset: true,
|
|
250
|
+
preset: :"CLIMATE_PRESET_#{value.to_s.upcase}"))
|
|
251
|
+
else
|
|
252
|
+
device.send(Api::ClimateCommandRequest.new(key:,
|
|
253
|
+
has_custom_preset: true,
|
|
254
|
+
custom_preset: value.to_s))
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Cover < Entity
|
|
6
|
+
include HasDeviceClass
|
|
7
|
+
include HasAssumedState
|
|
8
|
+
|
|
9
|
+
attr_reader :position, :tilt, :current_operation
|
|
10
|
+
|
|
11
|
+
def initialize(_device, list_entities_response)
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
@supports_position = list_entities_response.supports_position
|
|
15
|
+
@supports_stop = list_entities_response.supports_stop
|
|
16
|
+
@supports_tilt = list_entities_response.supports_tilt
|
|
17
|
+
@position = @tilt = @current_operation = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def position?
|
|
21
|
+
@supports_position
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def tilt?
|
|
25
|
+
@supports_tilt
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def stop?
|
|
29
|
+
@supports_stop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def formatted_state
|
|
33
|
+
formatted_segments.join(" ")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def formatted_segments
|
|
37
|
+
segments = [formatted_position_segment]
|
|
38
|
+
if tilt?
|
|
39
|
+
segments << "-"
|
|
40
|
+
segments << formatted_tilt_segment
|
|
41
|
+
end
|
|
42
|
+
segments << formatted_operation_segment
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def formatted_position_segment
|
|
46
|
+
if position?
|
|
47
|
+
formatted_percentage_segment(position)
|
|
48
|
+
elsif position == 1.0 # rubocop:disable Lint/FloatComparison
|
|
49
|
+
"OPEN"
|
|
50
|
+
elsif position == 0.0
|
|
51
|
+
"CLOSED"
|
|
52
|
+
else
|
|
53
|
+
"-"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def formatted_tilt_segment
|
|
58
|
+
formatted_percentage_segment(tilt)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def formatted_operation_segment
|
|
62
|
+
"(#{current_operation || "-"})"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update(state_response)
|
|
66
|
+
@position = state_response.position if state_response.position
|
|
67
|
+
@tilt = state_response.tilt if tilt?
|
|
68
|
+
|
|
69
|
+
@current_operation = if state_response.current_operation == :COVER_OPERATION_IDLE
|
|
70
|
+
:idle
|
|
71
|
+
else
|
|
72
|
+
state_response.current_operation[19..].downcase.to_sym
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def command(position: nil, tilt: nil)
|
|
77
|
+
command = Api::CoverCommandRequest.new(key:)
|
|
78
|
+
if position
|
|
79
|
+
command.has_position = true
|
|
80
|
+
command.position = position
|
|
81
|
+
end
|
|
82
|
+
if tilt
|
|
83
|
+
command.has_tilt = true
|
|
84
|
+
command.tilt = tilt
|
|
85
|
+
end
|
|
86
|
+
device.send(command)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def open
|
|
90
|
+
device.send(Api::CoverCommandRequest.new(key:,
|
|
91
|
+
has_legacy_command: true,
|
|
92
|
+
legacy_command: :LEGACY_COVER_COMMAND_OPEN,
|
|
93
|
+
has_position: true,
|
|
94
|
+
position: 1.0))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def close
|
|
98
|
+
device.send(Api::CoverCommandRequest.new(key:,
|
|
99
|
+
has_legacy_command: true,
|
|
100
|
+
legacy_command: :LEGACY_COVER_COMMAND_CLOSE,
|
|
101
|
+
has_position: true,
|
|
102
|
+
position: 0.0))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def stop
|
|
106
|
+
device.send(Api::CoverCommandRequest.new(key:,
|
|
107
|
+
has_legacy_command: true,
|
|
108
|
+
legacy_command: :LEGACY_COVER_COMMAND_STOP,
|
|
109
|
+
stop: true))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def formatted_percentage_segment(value)
|
|
115
|
+
"#{if value.nil?
|
|
116
|
+
"_"
|
|
117
|
+
elsif value.finite?
|
|
118
|
+
(value * 100).round
|
|
119
|
+
else
|
|
120
|
+
value
|
|
121
|
+
end}%"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def inspection_vars
|
|
125
|
+
super + %i[position tilt current_operation]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def hideable?(var, val)
|
|
129
|
+
super ||
|
|
130
|
+
(var == :position && !position?) ||
|
|
131
|
+
(var == :tilt && !tilt?)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Date < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
def update(state_response)
|
|
9
|
+
@state = if state_response.missing_state
|
|
10
|
+
nil
|
|
11
|
+
else
|
|
12
|
+
::Date.new(state_response.year,
|
|
13
|
+
state_response.month,
|
|
14
|
+
state_response.day)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def command(state)
|
|
19
|
+
device.send(Api::DateCommandRequest.new(key:, year: state.year, month: state.month, day: state.day))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class DateTime < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
def update(state_response)
|
|
9
|
+
@state = if state_response.missing_state
|
|
10
|
+
nil
|
|
11
|
+
else
|
|
12
|
+
::Time.at(state_response.epoch_seconds)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def command(state)
|
|
17
|
+
device.send(Api::DateTimeCommandRequest.new(key:, epoch_seconds: state.to_i))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Fan < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
attr_reader :speed_count,
|
|
9
|
+
:preset_modes,
|
|
10
|
+
:speed,
|
|
11
|
+
:direction,
|
|
12
|
+
:preset_mode
|
|
13
|
+
|
|
14
|
+
def initialize(_device, list_entities_response)
|
|
15
|
+
super
|
|
16
|
+
|
|
17
|
+
@supports_oscillation = list_entities_response.supports_oscillation
|
|
18
|
+
@supports_speed = list_entities_response.supports_speed
|
|
19
|
+
@speed_count = list_entities_response.supported_speed_count if speed?
|
|
20
|
+
@speed_count = 3 if speed_count.zero? && speed?
|
|
21
|
+
@preset_modes = list_entities_response.supported_preset_modes.map(&:freeze).freeze
|
|
22
|
+
|
|
23
|
+
@oscillating = @speed = @direction = @preset_mode = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def oscillation?
|
|
27
|
+
@supports_oscillation
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def speed?
|
|
31
|
+
@supports_speed
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def oscillating?
|
|
35
|
+
@oscillating
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def formatted_state
|
|
39
|
+
result = case state
|
|
40
|
+
when nil then "-"
|
|
41
|
+
when true then "on"
|
|
42
|
+
else "off"
|
|
43
|
+
end
|
|
44
|
+
if speed?
|
|
45
|
+
result +=
|
|
46
|
+
if speed_count == 100
|
|
47
|
+
" #{speed || "-"}%"
|
|
48
|
+
else
|
|
49
|
+
" #{speed || "-"}/#{speed_count}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
result += " oscillating" if oscillating?
|
|
53
|
+
result += " reversed" if direction == :reverse
|
|
54
|
+
result += " (#{preset_mode})" if preset_mode
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def update(state_response)
|
|
59
|
+
@state = state_response.state
|
|
60
|
+
@oscillating = state_response.oscillating
|
|
61
|
+
@speed = state_response.speed_level
|
|
62
|
+
if speed? && speed.nil?
|
|
63
|
+
@speed = { FAN_SPEED_LOW: 1,
|
|
64
|
+
FAN_SPEED_MEDIUM: 2,
|
|
65
|
+
FAN_SPEED_HIGH: 3 }[state_response.speed]
|
|
66
|
+
end
|
|
67
|
+
@direction = state_response.direction[14..].downcase.to_sym
|
|
68
|
+
@preset_mode = state_response.preset_mode.empty? ? nil : state_response.preset_mode
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def inspection_vars
|
|
74
|
+
super + %i[oscillation? speed_count preset_modes speed oscillating? direction preset_mode]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def hideable?(var, val)
|
|
78
|
+
super ||
|
|
79
|
+
(var == :oscillation && !oscillation?) ||
|
|
80
|
+
(var == :speed_count && !speed?) ||
|
|
81
|
+
(var == :preset_modes && preset_modes.empty?) ||
|
|
82
|
+
(var == :speed && !speed?) ||
|
|
83
|
+
(var == :oscillating && !oscillating?) ||
|
|
84
|
+
(var == :direction && direction == :forward) ||
|
|
85
|
+
(var == :preset_mode && preset_mode.nil?)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ESPHome
|
|
4
|
+
module Entities
|
|
5
|
+
class Light < Entity
|
|
6
|
+
include HasState
|
|
7
|
+
|
|
8
|
+
attr_reader :supported_color_modes,
|
|
9
|
+
:mireds_range,
|
|
10
|
+
:effects,
|
|
11
|
+
:brightness,
|
|
12
|
+
:color_mode,
|
|
13
|
+
:color_brightness,
|
|
14
|
+
:color_temperature,
|
|
15
|
+
:red,
|
|
16
|
+
:green,
|
|
17
|
+
:blue,
|
|
18
|
+
:white,
|
|
19
|
+
:cold_white,
|
|
20
|
+
:warm_white,
|
|
21
|
+
:transition_length,
|
|
22
|
+
:flash_effect,
|
|
23
|
+
:effect
|
|
24
|
+
|
|
25
|
+
def initialize(_device, list_entities_response)
|
|
26
|
+
super
|
|
27
|
+
|
|
28
|
+
@supported_color_modes = list_entities_response.supported_color_modes.map { |mode| mode[11..].downcase.to_sym }
|
|
29
|
+
if @supported_color_modes.empty?
|
|
30
|
+
@supported_color_modes << :brightness if list_entities_response.legacy_supports_brightness
|
|
31
|
+
@supported_color_modes << :rgb if list_entities_response.legacy_supports_rgb
|
|
32
|
+
@supported_color_modes << :white if list_entities_response.legacy_supports_white
|
|
33
|
+
@supported_color_modes << :color_temperature if list_entities_response.legacy_supports_color_temperature
|
|
34
|
+
@supported_color_modes = [:on_off] if @supported_color_modes.empty?
|
|
35
|
+
end
|
|
36
|
+
@supported_color_modes = @supported_color_modes.to_set { |mode| transform_color_mode(mode) }.freeze
|
|
37
|
+
|
|
38
|
+
@mireds_range = Range.new(list_entities_response.min_mireds, list_entities_response.max_mireds)
|
|
39
|
+
|
|
40
|
+
@effects = list_entities_response.effects.map(&:freeze).freeze
|
|
41
|
+
|
|
42
|
+
@brightness = @color_temperature = @red = @green = @blue = @white = @cold_white = @warm_white = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def brightness?
|
|
46
|
+
@supported_color_modes.include?(:brightness)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def white?
|
|
50
|
+
@supported_color_modes.intersect?(%i[white ww rgbw rgbww])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def rgb?
|
|
54
|
+
@supported_color_modes.intersect?(%i[rgb rgbw rgb_color_temperature rgbww])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def color_temperature?
|
|
58
|
+
@supported_color_modes.intersect?(%i[color_temperature rgb_color_temperature])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ww?
|
|
62
|
+
@supported_color_modes.intersect?(%i[ww rgbww])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update(state_response)
|
|
66
|
+
@state = state_response.state
|
|
67
|
+
@brightness = state_response.brightness if brightness?
|
|
68
|
+
@color_mode = state_response.color_mode
|
|
69
|
+
if rgb?
|
|
70
|
+
@color_brightness = state_response.color_brightness
|
|
71
|
+
@red = state_response.red
|
|
72
|
+
@green = state_response.green
|
|
73
|
+
@blue = state_response.blue
|
|
74
|
+
end
|
|
75
|
+
@white = state_response.white if white?
|
|
76
|
+
@color_temperature = state_response.color_temperature if color_temperature?
|
|
77
|
+
if ww?
|
|
78
|
+
@cold_white = state_response.cold_white
|
|
79
|
+
@warm_white = state_response.warm_white
|
|
80
|
+
end
|
|
81
|
+
@effect = state_response.effect.empty? ? nil : state_response.effect
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def formatted_state
|
|
85
|
+
result = case state
|
|
86
|
+
when nil then "-"
|
|
87
|
+
when true then "on"
|
|
88
|
+
else "off"
|
|
89
|
+
end
|
|
90
|
+
result += " #{brightness || color_brightness || "-"}%" if brightness?
|
|
91
|
+
|
|
92
|
+
if effect
|
|
93
|
+
result += effect
|
|
94
|
+
else
|
|
95
|
+
result += " #{color_temperature || "-"} mired" if color_temperature?
|
|
96
|
+
|
|
97
|
+
color = []
|
|
98
|
+
color.push(red, green, blue) if rgb?
|
|
99
|
+
color.push(white) if white?
|
|
100
|
+
color.push(cold_white, warm_white) if ww?
|
|
101
|
+
result += " (#{color.map { |c| c || "-" }.join(",")})" unless color.empty?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
result += " @ #{transition_length} ms" if transition_length
|
|
105
|
+
result += " (flash)" if flash_effect
|
|
106
|
+
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
def transform_color_mode(mode)
|
|
113
|
+
case mode
|
|
114
|
+
when :cold_warm_white then :ww
|
|
115
|
+
when :rgb_white then :rgbw
|
|
116
|
+
when :rgb_cold_warm_white then :rgbww
|
|
117
|
+
else mode
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def inspection_vars
|
|
122
|
+
super + %i[supported_color_modes
|
|
123
|
+
mireds_range
|
|
124
|
+
effects
|
|
125
|
+
brightness
|
|
126
|
+
color_mode
|
|
127
|
+
color_brightness
|
|
128
|
+
color_temperature
|
|
129
|
+
red
|
|
130
|
+
green
|
|
131
|
+
blue
|
|
132
|
+
white
|
|
133
|
+
cold_white
|
|
134
|
+
warm_white
|
|
135
|
+
transition_length
|
|
136
|
+
flash_effect
|
|
137
|
+
effect]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def hideable?(var, val)
|
|
141
|
+
super ||
|
|
142
|
+
(var == :supported_color_modes && supported_color_modes.size <= 1) ||
|
|
143
|
+
(var == :mireds_range && !color_temperature?) ||
|
|
144
|
+
(var == :effects && effects.empty?) ||
|
|
145
|
+
(var == :brightness && !brightness?) ||
|
|
146
|
+
(var == :color_mode && supported_color_modes.size <= 1) ||
|
|
147
|
+
(var == :color_brightness && !brightness? && !rgb?) ||
|
|
148
|
+
(var == :color_temperature && !color_temperature?) ||
|
|
149
|
+
(%i[red green blue].include?(var) && !rgb?) ||
|
|
150
|
+
(var == :white && !white?) ||
|
|
151
|
+
(%(cold_white warm_white).include?(var) && !ww?) ||
|
|
152
|
+
(var == :transition_length && !transition_length) ||
|
|
153
|
+
(var == :flash_effect && !flash_effect) ||
|
|
154
|
+
(var == :effect && effects.empty?)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|