lib/meshtastic/stream_interface.rb



# frozen_string_literal: true

require 'meshtastic/mesh_pb'

# Plugin used to interact with Meshtastic nodes
module Meshtastic
  class StreamInterface
    attr_accessor :cur_log_line,
                  :is_windows11,
                  :rx_buf,
                  :stream,
                  :want_exit

    def initialize(opts = {})
      debug_out = opts[:debug_out]
      no_proto = false if opts[:no_proto].nil?
      no_proto = true if opts[:no_proto]

      connect_now = true if opts[:connect_now].nil?
      connect_now = false if opts[:connect_now]

      no_nodes = false if opts[:no_nodes].nil?
      no_nodes = true if opts[:no_nodes]
      # Note: In Ruby, we don't need to explicitly define a type hint for self.
      raise Exception("StreamInterface is now abstract (to update existing code create SerialInterface instead)") if !defined?(@stream) && !no_proto

      @stream = nil
      @rx_buf = []
      @want_exit = false
      @is_windows11 = RUBY_PLATFORM =~ /win32/
      @cur_log_line = ""

      # Note: Ruby's threading API is different from Python. We use the Thread class instead of threading.Thread.
      rx_thread = Thread.new do
        reader
      end

      if connect_now
        connect
        unless no_proto
          wait_for_config
        end
      end
    rescue StandardError => e
      raise e
    end

    # Supported Method Parameters::
    # packet_id = Meshtastic.generate_packet_id(
    #   last_packet_id: 'optional - Last Packet ID (Default: 0)'
    # )
    def connect(opts = {})
      # Send some bogus UART characters to force a sleeping device to wake, and
      # if the reading statemachine was parsing a bad packet make sure we write enough start bytes to force it to resync (we don't use START1 because we want to ensure it is looking for START1)
      p = [START2] * 32
      self._write_bytes(p)

      sleep(0.1) # wait 100ms to give device time to start running

      @rx_thread.start
      mui = Meshtastic::MeshInterface.new
      mui.start_config
    rescue StandardError => e
      raise e
    end

    # Supported Method Parameters::
    # Meshtastic::StreamInterface.reader
    def reader
      loop do
        break if @want_exit
        # Read from stream and handle data
        # This should be implemented based on how you handle reading from @stream
        data = @stream.read(1) # Example: read one byte at a time
        @rx_buf << data if data

        # Here you would parse the data according to your protocol
        # This is just a placeholder for the actual reading logic

        # Yield for other threads
        sleep(0.01)
      end
    rescue StandardError => e
      raise e
    end

    def write_bytes(opts = {})
      bytes = opts[:bytes]
      @stream.write(bytes) if @stream
      @stream.flush if @stream
    end

    def read_bytes(opts = {})
      length = opts[:length]
      @stream.read(length) if @stream
    end

    def send_to_radio_impl(opts = {})
      to_radio = opts[:to_radio]
      # Convert to_radio to bytes, assuming it's a proto message in Ruby
      # This example assumes `to_radio` has a method to serialize to string
      b = to_radio.to_s
      buf_len = b.length
      header = [0x94, 0xC3, (buf_len >> 8) & 0xFF, buf_len & 0xFF].pack('C*')
      write_bytes(header + b)
    end

    def close
      @want_exit = true
      @rx_thread.join if @rx_thread && @rx_thread != Thread.current
      @stream&.close
    end

    # Author(s):: 0day Inc. <support@0dayinc.com>

    def authors
      "AUTHOR(S):
        0day Inc. <support@0dayinc.com>
      "
    end

    # Display Usage for this Module

    def help
      puts "USAGE:
        #{self}.connect

        #{self}.authors
      "
    end
  end
end