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

Raises:

  • (ArgumentError)


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
67
# 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
  raise ArgumentError, "missing encryption key" unless encryption_key

  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



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
199
# File 'lib/esphome/serial_proxy.rb', line 172

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)


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

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

#flushObject

Raises:

  • (EOFError)


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

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



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

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



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

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)


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
274
# File 'lib/esphome/serial_proxy.rb', line 247

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



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

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

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

Raises:

  • (ArgumentError)


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

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)


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

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

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

Raises:

  • (ArgumentError)


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
144
# File 'lib/esphome/serial_proxy.rb', line 117

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



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

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

#ungetc(char) ⇒ Object



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

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

#wait_readable(timeout = nil) ⇒ Object



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

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)


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

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