lignite 0.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 +7 -0
- data/.gitignore +7 -0
- data/COPYING +674 -0
- data/Gemfile +3 -0
- data/README.md +51 -0
- data/Rakefile +6 -0
- data/VERSION +1 -0
- data/bin/ev3tool +70 -0
- data/data/ev3.yml +10103 -0
- data/data/lignite-btaddr +7 -0
- data/data/sysops.yml +290 -0
- data/examples/hello.rb +8 -0
- data/examples/lights.rb +10 -0
- data/examples/motors.rb +19 -0
- data/examples/sound.rb +9 -0
- data/examples/sys_list_files.rb +16 -0
- data/lib/lignite.rb +30 -0
- data/lib/lignite/assembler.rb +49 -0
- data/lib/lignite/body_compiler.rb +42 -0
- data/lib/lignite/bytes.rb +35 -0
- data/lib/lignite/connection.rb +13 -0
- data/lib/lignite/connection/bluetooth.rb +37 -0
- data/lib/lignite/connection/usb.rb +74 -0
- data/lib/lignite/direct_commands.rb +26 -0
- data/lib/lignite/logger.rb +15 -0
- data/lib/lignite/message.rb +100 -0
- data/lib/lignite/message_sender.rb +92 -0
- data/lib/lignite/op_compiler.rb +224 -0
- data/lib/lignite/rbf_object.rb +33 -0
- data/lib/lignite/system_commands.rb +103 -0
- data/lib/lignite/variables.rb +27 -0
- data/lib/lignite/version.rb +4 -0
- data/lignite.gemspec +74 -0
- data/spec/assembler_spec.rb +24 -0
- data/spec/data/HelloWorld-subop.rb +6 -0
- data/spec/data/HelloWorld-subop.rbf +0 -0
- data/spec/data/HelloWorld.lms +7 -0
- data/spec/data/HelloWorld.rb +6 -0
- data/spec/data/HelloWorld.rbf +0 -0
- data/spec/data/VernierReadout.lms +31 -0
- data/spec/data/VernierReadout.rb +27 -0
- data/spec/data/VernierReadout.rbf +0 -0
- data/spec/spec_helper.rb +26 -0
- metadata +158 -0
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
              # Extends {OpCompiler} by
         | 
| 3 | 
            +
              # - variable declarations: {VariableDeclarer}
         | 
| 4 | 
            +
              # - high level flow control: {#loop}
         | 
| 5 | 
            +
              class BodyCompiler
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # {#locals} are {Variables}
         | 
| 8 | 
            +
                module VariableDeclarer
         | 
| 9 | 
            +
                  def data32(id)
         | 
| 10 | 
            +
                    locals.add(id, 4)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def datas(id, size)
         | 
| 14 | 
            +
                    locals.add(id, size)
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                include VariableDeclarer
         | 
| 19 | 
            +
                attr_reader :bytes
         | 
| 20 | 
            +
                attr_reader :locals
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def initialize(locals)
         | 
| 23 | 
            +
                  @bytes = ""
         | 
| 24 | 
            +
                  @locals = locals
         | 
| 25 | 
            +
                  @op_compiler = OpCompiler.new(nil, @locals)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                def loop(&body)
         | 
| 29 | 
            +
                  subc = BodyCompiler.new(@locals)
         | 
| 30 | 
            +
                  subc.instance_exec(&body)
         | 
| 31 | 
            +
                  @bytes << subc.bytes
         | 
| 32 | 
            +
                  # the jump takes up 4 bytes: JR, LC2, LO, HI
         | 
| 33 | 
            +
                  jr(Complex(- (subc.bytes.bytesize + 4), 2))
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def method_missing(name, *args)
         | 
| 37 | 
            +
                  super unless @op_compiler.respond_to?(name)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  @bytes += @op_compiler.send(name, *args)
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
              module Bytes
         | 
| 3 | 
            +
                def u8(n)
         | 
| 4 | 
            +
                  (n & 0xff).chr
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def u16(n)
         | 
| 8 | 
            +
                  u8(n & 0xff) + u8(n >> 8)
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def u32(n)
         | 
| 12 | 
            +
                  u16(n & 0xffff) + u16(n >> 16)
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def f32(float)
         | 
| 16 | 
            +
                  [float].pack("e")
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def unpack_u8(s)
         | 
| 20 | 
            +
                  s.unpack("C").first
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def unpack_u16(s)
         | 
| 24 | 
            +
                  s.unpack("S<").first
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def unpack_u32(s)
         | 
| 28 | 
            +
                  s.unpack("L<").first
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def hexdump(s)
         | 
| 32 | 
            +
                  s.unpack("H*").first
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
              class Connection
         | 
| 3 | 
            +
                # @return [Connection] Try a {Usb} connection first, then a {Bluetooth} one.
         | 
| 4 | 
            +
                def self.create
         | 
| 5 | 
            +
                  @c ||= begin
         | 
| 6 | 
            +
                           Usb.new
         | 
| 7 | 
            +
                         rescue NoUsbDevice
         | 
| 8 | 
            +
                           Bluetooth.new
         | 
| 9 | 
            +
                         end
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                # FIXME: how to close and reopen a connection?
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require "socket"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Lignite
         | 
| 4 | 
            +
              class Connection
         | 
| 5 | 
            +
                class Bluetooth < Connection
         | 
| 6 | 
            +
                  AF_BLUETOOTH = 31
         | 
| 7 | 
            +
                  BTPROTO_RFCOMM = 3
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  # @param address [String] "11:22:33:44:55:66"
         | 
| 10 | 
            +
                  def initialize(address = address_from_file)
         | 
| 11 | 
            +
                    @sock = Socket.new(AF_BLUETOOTH, :STREAM, BTPROTO_RFCOMM)
         | 
| 12 | 
            +
                    addr_b = address.split(/:/).map { |x| x.to_i(16) }
         | 
| 13 | 
            +
                    channel = 1
         | 
| 14 | 
            +
                    sockaddr = [AF_BLUETOOTH, 0, *addr_b.reverse, channel, 0].pack("C*")
         | 
| 15 | 
            +
                    # common exceptions:
         | 
| 16 | 
            +
                    # "Errno::EHOSTUNREACH: No route to host": BT is disabled;
         | 
| 17 | 
            +
                    #   use `hciconfig hci0 up`
         | 
| 18 | 
            +
                    # "Errno::EHOSTDOWN: Host is down": Turn the brick on, enable BT
         | 
| 19 | 
            +
                    @sock.connect(sockaddr)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def address_from_file
         | 
| 23 | 
            +
                    fn = "#{ENV['HOME']}/.config/lignite-btaddr"
         | 
| 24 | 
            +
                    s = File.read(fn)
         | 
| 25 | 
            +
                    s.lines.grep(/^[0-9a-fA-F]/).first.strip
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def read(n)
         | 
| 29 | 
            +
                    @sock.recv(n)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def write(s)
         | 
| 33 | 
            +
                    @sock.write(s)
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,74 @@ | |
| 1 | 
            +
            # https://github.com/larskanis/libusb
         | 
| 2 | 
            +
            require "libusb"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Lignite
         | 
| 5 | 
            +
              class NoUsbDevice < RuntimeError
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              class Connection
         | 
| 9 | 
            +
                class Usb < Connection
         | 
| 10 | 
            +
                  include Logger
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # To get to the endpoint we need to descend down the hierarchy of
         | 
| 13 | 
            +
                  # 1) Device
         | 
| 14 | 
            +
                  VENDOR_LEGO = 0x0694
         | 
| 15 | 
            +
                  PRODUCT_EV3 = 5
         | 
| 16 | 
            +
                  # 2) Configuration, 1-based
         | 
| 17 | 
            +
                  CONFIGURATION_EV3 = 1
         | 
| 18 | 
            +
                  # 3) Interface, 0-based
         | 
| 19 | 
            +
                  INTERFACE_EV3 = 0
         | 
| 20 | 
            +
                  # 4) Alternate setting, 0-based
         | 
| 21 | 
            +
                  SETTING_EV3 = 0
         | 
| 22 | 
            +
                  # 5) Endpoint, 0-based
         | 
| 23 | 
            +
                  ENDPOINT_EV3 = 1
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  attr_reader :device, :interface, :out_ep, :in_ep
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def initialize
         | 
| 28 | 
            +
                    usb = LIBUSB::Context.new
         | 
| 29 | 
            +
                    @device = usb.devices(idVendor: VENDOR_LEGO, idProduct: PRODUCT_EV3).first
         | 
| 30 | 
            +
                    raise Lignite::NoUsbDevice if @device.nil?
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    ## Because multiple configs are rare, the library allows to omit this:
         | 
| 33 | 
            +
                    ## device.set_configuration(CONFIGURATION_EV3)
         | 
| 34 | 
            +
                    @interface = @device.interfaces[INTERFACE_EV3]
         | 
| 35 | 
            +
                    eps = @interface.endpoints
         | 
| 36 | 
            +
                    @out_ep = eps.find { |e| e.direction == :out}
         | 
| 37 | 
            +
                    @in_ep = eps.find { |e| e.direction == :in}
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  # @return [Integer] number of bytes written
         | 
| 41 | 
            +
                  def write(data)
         | 
| 42 | 
            +
                    written = nil
         | 
| 43 | 
            +
                    @device.open do |devh|
         | 
| 44 | 
            +
                      devh.auto_detach_kernel_driver = true
         | 
| 45 | 
            +
                      devh.claim_interface(@interface) do
         | 
| 46 | 
            +
                        written = devh.interrupt_transfer(endpoint: @out_ep, dataOut: data)
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
                    written
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  # @return [String]
         | 
| 53 | 
            +
                  def read(bytes = nil)
         | 
| 54 | 
            +
                    got = nil
         | 
| 55 | 
            +
                    @device.open do |devh|
         | 
| 56 | 
            +
                      devh.auto_detach_kernel_driver = true
         | 
| 57 | 
            +
                      devh.claim_interface(@interface) do
         | 
| 58 | 
            +
                        begin
         | 
| 59 | 
            +
                          got = devh.interrupt_transfer(endpoint: @in_ep, dataIn: bytes)
         | 
| 60 | 
            +
                        rescue LIBUSB::Error => e
         | 
| 61 | 
            +
                          if e.transferred.is_a? String
         | 
| 62 | 
            +
                            got = e.transferred
         | 
| 63 | 
            +
                          else
         | 
| 64 | 
            +
                            raise
         | 
| 65 | 
            +
                          end
         | 
| 66 | 
            +
                        end
         | 
| 67 | 
            +
                      end
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                    logger.debug "Read returning #{got.bytesize} bytes"
         | 
| 70 | 
            +
                    got
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
              # FIXME: cannot handle replies
         | 
| 3 | 
            +
              class DirectCommands
         | 
| 4 | 
            +
                # @param conn [Connection]
         | 
| 5 | 
            +
                def initialize(conn = Connection.create)
         | 
| 6 | 
            +
                  @op_compiler = OpCompiler.new
         | 
| 7 | 
            +
                  @sender = MessageSender.new(conn)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def block(&body)
         | 
| 11 | 
            +
                  locals = Variables.new
         | 
| 12 | 
            +
                  bodyc = BodyCompiler.new(locals)
         | 
| 13 | 
            +
                  bodyc.instance_exec(&body)
         | 
| 14 | 
            +
                  @sender.direct_command(bodyc.bytes, local_size: locals.bytesize)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def method_missing(name, *args)
         | 
| 18 | 
            +
                  if @op_compiler.respond_to?(name)
         | 
| 19 | 
            +
                    insb = @op_compiler.send(name, *args)
         | 
| 20 | 
            +
                    @sender.direct_command(insb)
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    super
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require "logger"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Lignite
         | 
| 4 | 
            +
              module Logger
         | 
| 5 | 
            +
                def self.default_logger
         | 
| 6 | 
            +
                  logger = ::Logger.new(STDERR)
         | 
| 7 | 
            +
                  logger.level = $VERBOSE ? ::Logger::DEBUG : ::Logger::INFO
         | 
| 8 | 
            +
                  logger
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def logger
         | 
| 12 | 
            +
                  @logger ||= Logger.default_logger
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,100 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
              # A Message has 3 common parts:
         | 
| 3 | 
            +
              # - length u16, (not including the length itself);
         | 
| 4 | 
            +
              #   this is added by {MessageSender#send}
         | 
| 5 | 
            +
              #   and stripped by {MessageSender#receive}
         | 
| 6 | 
            +
              # - msgid, u16
         | 
| 7 | 
            +
              # - type, u8
         | 
| 8 | 
            +
              # and then a type-specific body.
         | 
| 9 | 
            +
              # It is sent or received via {MessageSender}
         | 
| 10 | 
            +
              class Message
         | 
| 11 | 
            +
                include Bytes
         | 
| 12 | 
            +
                extend Bytes
         | 
| 13 | 
            +
                extend Logger
         | 
| 14 | 
            +
                @msg_counter = rand(65535)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def self.msgid
         | 
| 17 | 
            +
                  @msg_counter += 1
         | 
| 18 | 
            +
                  logger.debug "MSGID #{@msg_counter}"
         | 
| 19 | 
            +
                  @msg_counter
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                attr_reader :msgid, :type, :body
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def initialize(type:, body:)
         | 
| 25 | 
            +
                  @msgid = self.class.msgid
         | 
| 26 | 
            +
                  @type = type
         | 
| 27 | 
            +
                  @body = body
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # not including the length
         | 
| 31 | 
            +
                def bytes
         | 
| 32 | 
            +
                  u16(@msgid) + u8(@type) + @body
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def self.system_command_with_reply(body)
         | 
| 36 | 
            +
                  new(type: 0x01, body: body)
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def self.system_command_no_reply(body)
         | 
| 40 | 
            +
                  new(type: 0x81, body: body)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def self.direct_command_with_reply(body)
         | 
| 44 | 
            +
                  new(type: 0x00, body: body)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def self.direct_command_no_reply(body)
         | 
| 48 | 
            +
                  new(type: 0x80, body: body)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                # @param bytes [ByteString] does not include the length field
         | 
| 52 | 
            +
                def self.reply_from_bytes(bytes)
         | 
| 53 | 
            +
                  msgid = unpack_u16(bytes[0..1])
         | 
| 54 | 
            +
                  type = unpack_u8(bytes[2])
         | 
| 55 | 
            +
                  body = bytes[3..-1]
         | 
| 56 | 
            +
                  case type
         | 
| 57 | 
            +
                  when 0x03               # SYSTEM_REPLY
         | 
| 58 | 
            +
                    SystemReply.new(msgid: msgid, error: false, body: body)
         | 
| 59 | 
            +
                  when 0x05               # SYSTEM_REPLY_ERROR
         | 
| 60 | 
            +
                    SystemReply.new(msgid: msgid, error: true, body: body)
         | 
| 61 | 
            +
                  when 0x02               # DIRECT_REPLY
         | 
| 62 | 
            +
                    DirectReply.new(msgid: msgid, error: false, body: body)
         | 
| 63 | 
            +
                  when 0x04               # DIRECT_REPLY_ERROR
         | 
| 64 | 
            +
                    DirectReply.new(msgid: msgid, error: true, body: body)
         | 
| 65 | 
            +
                  else
         | 
| 66 | 
            +
                    raise "Unexpected reply type %x" % type
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
              class SystemReply < Message
         | 
| 72 | 
            +
                def initialize(msgid:, error:, body:)
         | 
| 73 | 
            +
                  @msgid = msgid
         | 
| 74 | 
            +
                  @error = error
         | 
| 75 | 
            +
                  @command = unpack_u8(body[0])
         | 
| 76 | 
            +
                  @status = unpack_u8(body[1])
         | 
| 77 | 
            +
                  @data = body[2..-1]
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                attr_reader :msgid, :error, :command, :status, :data
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def error?
         | 
| 83 | 
            +
                  @error
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              class DirectReply < Message
         | 
| 88 | 
            +
                def new(msgid:, error:, body:)
         | 
| 89 | 
            +
                  @msgid = msgid
         | 
| 90 | 
            +
                  @error = error
         | 
| 91 | 
            +
                  @globals = body
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                attr_reader :msgid, :error, :globals
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def error?
         | 
| 97 | 
            +
                  @error
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
            end
         | 
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            module Lignite
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # FIXME: Possibly merge with Connection (UsbConnection)
         | 
| 4 | 
            +
              class MessageSender
         | 
| 5 | 
            +
                include Bytes
         | 
| 6 | 
            +
                include Logger
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(connection)
         | 
| 9 | 
            +
                  @c = connection
         | 
| 10 | 
            +
                  @buf = ""
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def direct_command(instr_bytes, global_size: 0, local_size: 0)
         | 
| 14 | 
            +
                  body = u16(var_alloc(global_size: global_size, local_size: local_size)) +
         | 
| 15 | 
            +
                         instr_bytes
         | 
| 16 | 
            +
                  cmd = Message.direct_command_no_reply(body)
         | 
| 17 | 
            +
                  send(cmd.bytes)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def direct_command_with_reply(instr_bytes, global_size: 0, local_size: 0)
         | 
| 21 | 
            +
                  body = u16(var_alloc(global_size: global_size, local_size: local_size)) +
         | 
| 22 | 
            +
                         instr_bytes
         | 
| 23 | 
            +
                  cmd = Message.direct_command_with_reply(body)
         | 
| 24 | 
            +
                  send(cmd.bytes)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  reply = Message.reply_from_bytes(receive)
         | 
| 27 | 
            +
                  assert_match(reply.msgid, cmd.msgid, "Reply id")
         | 
| 28 | 
            +
                  if reply.error?
         | 
| 29 | 
            +
                    raise "VMError"         # no details?
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  reply.data
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def system_command_with_reply(instr_bytes)
         | 
| 36 | 
            +
                  cmd = Message.system_command_with_reply(instr_bytes)
         | 
| 37 | 
            +
                  send(cmd.bytes)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  reply = Message.reply_from_bytes(receive)
         | 
| 40 | 
            +
                  assert_match(reply.msgid, cmd.msgid, "Reply id")
         | 
| 41 | 
            +
                  assert_match(reply.command, unpack_u8(instr_bytes[0]), "Command num")
         | 
| 42 | 
            +
                  if reply.error?
         | 
| 43 | 
            +
                    raise "VMError, %u" % reply.status
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  reply.data
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                private
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def send(payload)
         | 
| 52 | 
            +
                  packet = u16(payload.bytesize) + payload
         | 
| 53 | 
            +
                  logger.debug "-> #{packet.inspect}"
         | 
| 54 | 
            +
                  @c.write(packet)
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                # read must not be called with a too low value :-/
         | 
| 58 | 
            +
                def bufread(n)
         | 
| 59 | 
            +
                  while n > @buf.bytesize
         | 
| 60 | 
            +
                    @buf += @c.read(10000)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  ret = @buf[0, n]
         | 
| 63 | 
            +
                  @buf = @buf[n..-1]
         | 
| 64 | 
            +
                  logger.debug "R<-(#{ret.bytesize})#{ret.inspect}"
         | 
| 65 | 
            +
                  ret
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def receive
         | 
| 69 | 
            +
                  size = nil
         | 
| 70 | 
            +
                  loop do
         | 
| 71 | 
            +
                    lenbuf = bufread(2)
         | 
| 72 | 
            +
                    size = unpack_u16(lenbuf)
         | 
| 73 | 
            +
                    break unless size.zero?
         | 
| 74 | 
            +
                    # leftover data?
         | 
| 75 | 
            +
                    @buf = ""
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  res = bufread(size)
         | 
| 79 | 
            +
                  res
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def var_alloc(global_size:, local_size:)
         | 
| 83 | 
            +
                  var_alloc = global_size & 0x3ff
         | 
| 84 | 
            +
                  var_alloc |= (local_size & 0x3f) << 10
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def assert_match(actual, expected, description)
         | 
| 88 | 
            +
                  return if actual == expected
         | 
| 89 | 
            +
                  raise "#{description} does not match, expected #{expected}, actual #{actual}"
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
            end
         |