Class: BWA::Client

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/bwa/client.rb

Constant Summary collapse

HEATING_MODES =
%I[ready rest ready_in_rest].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri) ⇒ Client

Returns a new instance of Client.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/bwa/client.rb', line 37

def initialize(uri)
  uri = URI.parse(uri)
  case uri.scheme
  when "tcp"
    require "socket"
    @io = TCPSocket.new(uri.host, uri.port || 4257)
  when "telnet", "rfc2217"
    require "net/telnet/rfc2217"
    @io = Net::Telnet::RFC2217.new(uri.host,
                                   port: uri.port || 23,
                                   baud: 115_200)
    @queue = []
  when "esphome"
    begin
      gem "esphome", "~> 1.1"
      require "esphome/serial_proxy"
    rescue LoadError
      warn "Please `gem install esphome ~> 1.1` before using an ESPHome serial proxy"
      exit 1
    end
    @io = ESPHome::SerialProxy.open(uri, baud: 115_200, data_bits: 8, parity: :none, stop_bits: 1)
    @queue = []
  else
    require "ccutrer-serialport"
    @io = CCutrer::SerialPort.new(uri.path, baud: 115_200, data_bits: 8, parity: :none, stop_bits: 1)
    @queue = []
  end
  @src = 0x0a
  @buffer = +""
end

Instance Attribute Details

#configurationObject (readonly)

Returns the value of attribute configuration.



13
14
15
# File 'lib/bwa/client.rb', line 13

def configuration
  @configuration
end

#control_configurationObject (readonly)

Returns the value of attribute control_configuration.



13
14
15
# File 'lib/bwa/client.rb', line 13

def control_configuration
  @control_configuration
end

#filter_cyclesObject (readonly)

Returns the value of attribute filter_cycles.



13
14
15
# File 'lib/bwa/client.rb', line 13

def filter_cycles
  @filter_cycles
end

#statusObject (readonly)

Returns the value of attribute status.



13
14
15
# File 'lib/bwa/client.rb', line 13

def status
  @status
end

Instance Method Details

#blower=(desired) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/bwa/client.rb', line 220

def blower=(desired)
  return unless status && configuration

  desired = 0 if desired == false
  desired = configuration.blower if desired == true
  desired = [desired, configuration.blower].min
  times = (desired - status.blower) % (configuration.blower + 1)
  times.times do |i|
    toggle_blower
    sleep(0.1) unless i == times - 1
  end
end

#drain_message_queueObject



118
119
120
# File 'lib/bwa/client.rb', line 118

def drain_message_queue
  poll while messages_pending?
end

#full_configuration?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/bwa/client.rb', line 68

def full_configuration?
  status && control_configuration && configuration && filter_cycles
end

#heating_mode=(desired) ⇒ Object

Raises:

  • (ArgumentError)


282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/bwa/client.rb', line 282

def heating_mode=(desired)
  raise ArgumentError, "heating_mode must be :ready or :rest" unless %I[ready rest].include?(desired)
  return unless status

  times = if (status.heating_mode == :ready && desired == :rest) ||
             (status.heating_mode == :rest && desired == :ready) ||
             (status.heating_mode == :ready_in_rest && desired == :rest)
            1
          elsif status.heating_mode == :ready_in_rest && desired == :ready
            2
          else
            0
          end
  times.times do |i|
    toggle_heating_mode
    sleep(0.1) unless i == times - 1
  end
end

#hold=(desired) ⇒ Object



233
234
235
236
237
238
# File 'lib/bwa/client.rb', line 233

def hold=(desired)
  return unless status
  return if status.hold == desired

  toggle_hold
end

#messages_pending?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/bwa/client.rb', line 114

def messages_pending?
  !!@io.wait_readable(0)
end

#mister=(desired) ⇒ Object



213
214
215
216
217
218
# File 'lib/bwa/client.rb', line 213

def mister=(desired)
  return unless status
  return if status.mister == desired

  toggle_mister
end

#pollObject



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/bwa/client.rb', line 72

def poll
  message = bytes_read = nil
  loop do
    message, bytes_read = Message.parse(@buffer)
    # discard how much we read
    @buffer = @buffer[bytes_read..] if bytes_read
    method = @io.respond_to?(:readpartial) ? :readpartial : :read
    unless message
      # one EOF is just serial ports saying they have no data;
      # several EOFs in a row is the file is dead and gone
      eofs = 0
      begin
        @buffer.concat(@io.__send__(method, 64 * 1024))
      rescue EOFError
        eofs += 1
        raise if eofs == 5

        @io.wait_readable(5)
        retry
      rescue Errno::ECONNRESET
        BWA.logger.debug "ECONNRESET occured; retrying"
        @io.wait_readable(5)
        retry
      end
      next
    end
    break
  end

  if message.is_a?(Messages::Ready) && (msg = @queue&.shift)
    unless BWA.verbosity < 1 && msg[3..4] == Messages::ControlConfigurationRequest::MESSAGE_TYPE
      BWA.logger.debug "wrote: #{BWA.raw2str(msg)}"
    end
    @io.write(msg)
  end
  @status = message.dup if message.is_a?(Messages::Status)
  @filter_cycles = message.dup if message.is_a?(Messages::FilterCycles)
  @control_configuration = message.dup if message.is_a?(Messages::ControlConfiguration)
  @configuration = message.dup if message.is_a?(Messages::ControlConfiguration2)
  message
end

#request_configurationObject



138
139
140
# File 'lib/bwa/client.rb', line 138

def request_configuration
  send_message(Messages::ConfigurationRequest.new)
end

#request_control_infoObject



146
147
148
# File 'lib/bwa/client.rb', line 146

def request_control_info
  send_message(Messages::ControlConfigurationRequest.new(1))
end

#request_control_info2Object



142
143
144
# File 'lib/bwa/client.rb', line 142

def request_control_info2
  send_message(Messages::ControlConfigurationRequest.new(2))
end

#request_filter_configurationObject



150
151
152
# File 'lib/bwa/client.rb', line 150

def request_filter_configuration
  send_message(Messages::ControlConfigurationRequest.new(3))
end

#send_message(message) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/bwa/client.rb', line 122

def send_message(message)
  message.src = @src
  full_message = message.serialize
  unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
    BWA.logger.info "  to spa: #{message.inspect}"
  end
  if @queue
    @queue.push(full_message)
  else
    unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
      BWA.logger.debug "wrote: #{BWA.raw2str(full_message)}"
    end
    @io.write(full_message)
  end
end

#set_pump(index, desired) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/bwa/client.rb', line 182

def set_pump(index, desired)
  return unless status && configuration

  desired = 0 if desired == false
  max_pump_speed = configuration.pumps[index]
  desired = max_pump_speed if desired == true
  desired = [desired, max_pump_speed].min

  # turn all pumps off with a single command
  return toggle_item(:soak) if desired.zero? && configuration.pumps.length == 1 && max_pump_speed != 1

  current_pump_speed = [status.pumps[index], max_pump_speed].min
  times = (desired - current_pump_speed) % (max_pump_speed + 1)
  times.times do |i|
    toggle_pump(index)
    sleep(0.1) unless i == times - 1
  end
end

#set_time(hour, minute, twenty_four_hour_time: false) ⇒ Object



250
251
252
# File 'lib/bwa/client.rb', line 250

def set_time(hour, minute, twenty_four_hour_time: false)
  send_message(Messages::SetTime.new(hour, minute, twenty_four_hour_time))
end

#target_temperature=(desired) ⇒ Object

high range is 80-106 for F, 26-40 for C (by 0.5) low range is 50-99 for F, 10-26 for C (by 0.5)



242
243
244
245
246
247
248
# File 'lib/bwa/client.rb', line 242

def target_temperature=(desired)
  return unless status
  return if status.target_temperature == desired

  desired *= 2 if (status && status.temperature_scale == :celsius) || desired < 50
  send_message(Messages::SetTargetTemperature.new(desired.round))
end

#temperature_range=(desired) ⇒ Object



270
271
272
273
274
275
# File 'lib/bwa/client.rb', line 270

def temperature_range=(desired)
  return unless status
  return if status.temperature_range == desired

  toggle_temperature_range
end

#temperature_scale=(scale) ⇒ Object

Raises:

  • (ArgumentError)


254
255
256
257
258
# File 'lib/bwa/client.rb', line 254

def temperature_scale=(scale)
  raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I[fahrenheit celsius].include?(scale)

  send_message(Messages::SetTemperatureScale.new(scale))
end

#toggle_aux(index) ⇒ Object



166
167
168
# File 'lib/bwa/client.rb', line 166

def toggle_aux(index)
  toggle_item(index + 0x16)
end

#toggle_blowerObject



174
175
176
# File 'lib/bwa/client.rb', line 174

def toggle_blower
  toggle_item(:blower)
end

#toggle_heating_modeObject



277
278
279
# File 'lib/bwa/client.rb', line 277

def toggle_heating_mode
  toggle_item(:heating_mode)
end

#toggle_holdObject



178
179
180
# File 'lib/bwa/client.rb', line 178

def toggle_hold
  toggle_item(:hold)
end

#toggle_item(item) ⇒ Object



154
155
156
# File 'lib/bwa/client.rb', line 154

def toggle_item(item)
  send_message(Messages::ToggleItem.new(item))
end

#toggle_light(index) ⇒ Object



162
163
164
# File 'lib/bwa/client.rb', line 162

def toggle_light(index)
  toggle_item(index + 0x11)
end

#toggle_misterObject



170
171
172
# File 'lib/bwa/client.rb', line 170

def toggle_mister
  toggle_item(:mister)
end

#toggle_pump(index) ⇒ Object



158
159
160
# File 'lib/bwa/client.rb', line 158

def toggle_pump(index)
  toggle_item(index + 0x04)
end

#toggle_temperature_rangeObject



266
267
268
# File 'lib/bwa/client.rb', line 266

def toggle_temperature_range
  toggle_item(0x50)
end

#update_filter_cycles(new_filter_cycles) ⇒ Object



260
261
262
263
264
# File 'lib/bwa/client.rb', line 260

def update_filter_cycles(new_filter_cycles)
  send_message(new_filter_cycles)
  @filter_cycles = new_filter_cycles.dup
  request_filter_configuration
end