Class: ESPHome::SerialProxy

Inherits:
Object
  • Object
show all
Defined in:
lib/esphome/serial_proxy.rb

Defined Under Namespace

Classes: WaitReadable

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#baudObject (readonly)

Returns the value of attribute baud.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def baud
  @baud
end

#data_bitsObject (readonly)

Returns the value of attribute data_bits.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def data_bits
  @data_bits
end

#deviceObject (readonly)

Returns the value of attribute device.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def device
  @device
end

#instanceObject (readonly)

Returns the value of attribute instance.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def instance
  @instance
end

#nameObject (readonly)

Returns the value of attribute name.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def name
  @name
end

#parityObject (readonly)

Returns the value of attribute parity.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def parity
  @parity
end

#port_typeObject (readonly)

Returns the value of attribute port_type.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def port_type
  @port_type
end

#stop_bitsObject (readonly)

Returns the value of attribute stop_bits.



14
15
16
# File 'lib/esphome/serial_proxy.rb', line 14

def stop_bits
  @stop_bits
end

Class Method Details

.open(address, encryption_key = nil, instance = nil, **kwargs) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
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/esphome/serial_proxy.rb', line 24

def open(address, encryption_key = nil, instance = nil, **kwargs)
  if address.is_a?(URI) && encryption_key.nil? && instance.nil?
    encryption_key = address.password
    instance = address.path[1..]
    instance = nil if instance.empty?
    address = address.host
  end

  device = Device.new(address, encryption_key)
  device.connect

  instance ||= 0
  io = device.serial_proxies[instance]
  begin
    io ||= device.serial_proxies[Integer(instance)]
  rescue ArgumentError
    # ignore; will raise below anyway
  end

  raise NoSuchSerialProxyError, "No serial proxy #{instance.inspect}" unless io

  io.set_modem_params(**kwargs) unless kwargs.empty?
  loop_thread = io.instance_variable_set(:@loop_thread, Thread.new do
    Thread.current.report_on_exception = false
    device.loop
  rescue IOError
    raise unless io.closed?
  end)
  io.open

  if block_given?
    begin
      yield io
    ensure
      io.close
      device.disconnect
      loop_thread.join
    end
    nil
  else
    io
  end
end

Instance Method Details

#closeObject



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/esphome/serial_proxy.rb', line 171

def close
  loop_thread = @mutex.synchronize do
    already_closed = @closed
    @closed = true
    @data_ready.broadcast
    [already_closed, @loop_thread]
  end

  return if loop_thread.first

  # We're in a SerialProxy.open block; just close the connection without unsubscribing
  if loop_thread.last
    device&.disconnect
    loop_thread.last.join
    @mutex.synchronize do
      @loop_thread = nil
    end
    return
  end
  return unless device&.connected?

  @request_mutex.synchronize do
    return unless device&.connected?

    device.send(Api::SerialProxyRequest.new(instance:,
                                            type: :SERIAL_PROXY_REQUEST_TYPE_UNSUBSCRIBE))
  end
end

#closed?Boolean

Returns:

  • (Boolean)


312
313
314
# File 'lib/esphome/serial_proxy.rb', line 312

def closed?
  @mutex.synchronize { @closed }
end

#flushObject

Raises:

  • (EOFError)


210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/esphome/serial_proxy.rb', line 210

def flush
  raise EOFError, "closed stream" if closed?
  raise MissingDeviceError unless device
  raise EOFError, "serial proxy disconnected" unless device.connected?

  @request_mutex.synchronize do
    wait(:SERIAL_PROXY_REQUEST_TYPE_FLUSH) do
      device.send(Api::SerialProxyRequest.new(instance:,
                                              type: :SERIAL_PROXY_REQUEST_TYPE_FLUSH))
    end
  end
end

#openObject



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/esphome/serial_proxy.rb', line 145

def open
  raise MissingProxyError unless device

  @request_mutex.synchronize do
    @mutex.synchronize do
      return self unless @closed

      @closed = false
      @data_ready.broadcast
    end

    return self unless device.connected?

    device.send(Api::SerialProxyRequest.new(instance:,
                                            type: :SERIAL_PROXY_REQUEST_TYPE_SUBSCRIBE))
  end

  self
rescue
  @mutex.synchronize do
    @closed = true
    @data_ready.broadcast
  end
  raise
end

#read(length, outbuf = +"")) ⇒ Object



223
224
225
226
227
# File 'lib/esphome/serial_proxy.rb', line 223

def read(length, outbuf = +"")
  readpartial(length, outbuf)
  outbuf.concat(readpartial(length - outbuf.bytesize)) while outbuf.bytesize < length
  outbuf
end

#read_nonblock(maxlen, outbuf = +"",, options = {}) ⇒ Object

Raises:

  • (ArgumentError)


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/esphome/serial_proxy.rb', line 246

def read_nonblock(maxlen, outbuf = +"", options = {})
  raise ArgumentError, "maxlen must be non-negative" unless maxlen >= 0

  if outbuf == ({ exception: false })
    options = outbuf
    outbuf = +""
  end

  return apply_outbuf(outbuf, +"".b) if maxlen.zero?

  loop do
    data = @mutex.synchronize do
      raise EOFError if @closed && @buffer.empty?

      @buffer.slice!(0, maxlen) unless @buffer.empty?
    end
    return apply_outbuf(outbuf, data) if data

    result = wait_readable(0)
    if result.nil?
      raise WaitReadable unless options[:exception] == false

      return :wait_readable
    end

    next
  end
end

#readbyteObject Also known as: getbyte



229
230
231
# File 'lib/esphome/serial_proxy.rb', line 229

def readbyte
  read(1)&.getbyte(0)
end

#readpartial(maxlen, outbuf = +"")) ⇒ Object

Raises:

  • (ArgumentError)


234
235
236
237
238
239
240
241
242
243
244
# File 'lib/esphome/serial_proxy.rb', line 234

def readpartial(maxlen, outbuf = +"")
  raise ArgumentError, "maxlen must be non-negative" unless maxlen >= 0

  data = wait_for_data do
    raise EOFError if @closed && @buffer.empty?
    break +"".b if maxlen.zero?
    break @buffer.slice!(0, maxlen) unless @buffer.empty?
  end

  apply_outbuf(outbuf, data)
end

#ready?Boolean

Returns:

  • (Boolean)


294
295
296
# File 'lib/esphome/serial_proxy.rb', line 294

def ready?
  !wait_readable(0).nil?
end

#set_modem_params(baud: nil, data_bits: nil, parity: nil, stop_bits: nil) ⇒ Object

Raises:

  • (ArgumentError)


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/esphome/serial_proxy.rb', line 116

def set_modem_params(baud: nil, data_bits: nil, parity: nil, stop_bits: nil)
  raise ArgumentError, "Parity must be :none, :even, or :odd" if !parity.nil? && !%i[none even odd].include?(parity)
  raise MissingProxyError unless device

  current_baud = nil
  current_data_bits = nil
  current_parity = nil
  current_stop_bits = nil
  @mutex.synchronize do
    @baud = baud || @baud || 115_200
    @data_bits = data_bits || @data_bits || 8
    @parity = parity || @parity || :none
    @stop_bits = stop_bits || @stop_bits || 1
    current_baud = @baud
    current_data_bits = @data_bits
    current_parity = @parity
    current_stop_bits = @stop_bits
  end

  return unless device.connected?

  device.send(Api::SerialProxyConfigureRequest.new(instance:,
                                                   baudrate: current_baud,
                                                   flow_control: false,
                                                   parity: normalize_serial_proxy_parity(current_parity),
                                                   stop_bits: current_stop_bits,
                                                   data_size: current_data_bits))
end

#ungetbyte(byte) ⇒ Object



298
299
300
301
302
303
# File 'lib/esphome/serial_proxy.rb', line 298

def ungetbyte(byte)
  @mutex.synchronize do
    @buffer.insert(0, Integer(byte).chr)
    @data_ready.broadcast
  end
end

#ungetc(char) ⇒ Object



305
306
307
308
309
310
# File 'lib/esphome/serial_proxy.rb', line 305

def ungetc(char)
  @mutex.synchronize do
    @buffer.insert(0, String(char))
    @data_ready.broadcast
  end
end

#wait_readable(timeout = nil) ⇒ Object



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/esphome/serial_proxy.rb', line 275

def wait_readable(timeout = nil)
  return self unless @buffer.empty?

  @mutex.synchronize do
    return self unless @buffer.empty?

    deadline = monotonic_time + timeout if timeout
    Kernel.loop do
      return nil if @closed || !device.connected?

      remaining = deadline && (deadline - monotonic_time)
      return nil if deadline && remaining <= 0

      @data_ready.wait(@mutex, remaining)
      return self unless @buffer.empty?
    end
  end
end

#write(data) ⇒ Object

Raises:

  • (EOFError)


200
201
202
203
204
205
206
207
208
# File 'lib/esphome/serial_proxy.rb', line 200

def write(data)
  raise EOFError, "closed stream" if closed?
  raise MissingDeviceError unless device
  raise EOFError, "serial proxy disconnected" unless device.connected?

  encoded = String(data).b
  device.send(Api::SerialProxyWriteRequest.new(instance:, data: encoded))
  encoded.bytesize
end