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,224 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Lignite
         
     | 
| 
      
 4 
     | 
    
         
            +
              class OpCompiler
         
     | 
| 
      
 5 
     | 
    
         
            +
                include Bytes
         
     | 
| 
      
 6 
     | 
    
         
            +
                include Logger
         
     | 
| 
      
 7 
     | 
    
         
            +
                extend Logger
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                class TODO < StandardError
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def self.load_const(name, value)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  raise "duplicate constant #{name}" if Lignite.const_defined?(name)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  Lignite.const_set(name, value)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def self.load_op(oname, odata)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  ovalue = odata["value"]
         
     | 
| 
      
 19 
     | 
    
         
            +
                  oparams = odata["params"]
         
     | 
| 
      
 20 
     | 
    
         
            +
                  p1 = oparams.first
         
     | 
| 
      
 21 
     | 
    
         
            +
                  if p1 && p1["type"] == "SUBP"
         
     | 
| 
      
 22 
     | 
    
         
            +
                    commands = p1["commands"]
         
     | 
| 
      
 23 
     | 
    
         
            +
                    commands.each do |cname, cdata|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      cvalue = cdata["value"]
         
     | 
| 
      
 25 
     | 
    
         
            +
                      load_const(cname, cvalue)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      cparams = cdata["params"]
         
     | 
| 
      
 27 
     | 
    
         
            +
                      define_op("#{oname}_#{cname}", ovalue, cvalue, cparams)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    end
         
     | 
| 
      
 29 
     | 
    
         
            +
                    define_multiop(oname, commands)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    define_op(oname, ovalue, nil, oparams)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def self.define_multiop(oname, commands)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  names = commands.map do |cname, cdata|
         
     | 
| 
      
 37 
     | 
    
         
            +
                    csym = cname.downcase.to_sym
         
     | 
| 
      
 38 
     | 
    
         
            +
                    cvalue = cdata["value"]
         
     | 
| 
      
 39 
     | 
    
         
            +
                    [cvalue, csym]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end.to_h
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  osym = oname.downcase.to_sym
         
     | 
| 
      
 43 
     | 
    
         
            +
                  define_method(osym) do |*args|
         
     | 
| 
      
 44 
     | 
    
         
            +
                    logger.debug "called #{osym} with #{args.inspect}"
         
     | 
| 
      
 45 
     | 
    
         
            +
                    cvalue = args.shift
         
     | 
| 
      
 46 
     | 
    
         
            +
                    csym = names.fetch(cvalue)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    send("#{osym}_#{csym}", *args)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def self.define_op(oname, ovalue, cvalue, params)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  check_arg_count = true
         
     | 
| 
      
 53 
     | 
    
         
            +
                  param_handlers = params.map do |par|
         
     | 
| 
      
 54 
     | 
    
         
            +
                    case par["type"]
         
     | 
| 
      
 55 
     | 
    
         
            +
                    when "PARLAB"           # a label, only one opcode
         
     | 
| 
      
 56 
     | 
    
         
            +
                      raise TODO
         
     | 
| 
      
 57 
     | 
    
         
            +
                    when "PARNO"            # the value says how many other params follow
         
     | 
| 
      
 58 
     | 
    
         
            +
                      check_arg_count = false
         
     | 
| 
      
 59 
     | 
    
         
            +
                      ->(x) { param_simple(x) }
         
     | 
| 
      
 60 
     | 
    
         
            +
                    when "PARS"             # string
         
     | 
| 
      
 61 
     | 
    
         
            +
                      raise TODO
         
     | 
| 
      
 62 
     | 
    
         
            +
                    when "PARV"             # value, type depends
         
     | 
| 
      
 63 
     | 
    
         
            +
                      raise TODO
         
     | 
| 
      
 64 
     | 
    
         
            +
                    when "PARVALUES"
         
     | 
| 
      
 65 
     | 
    
         
            +
                      raise TODO
         
     | 
| 
      
 66 
     | 
    
         
            +
                    when "PAR8", "PAR16", "PAR32", "PARF"
         
     | 
| 
      
 67 
     | 
    
         
            +
                      ->(x) { param_simple(x) }
         
     | 
| 
      
 68 
     | 
    
         
            +
                    else
         
     | 
| 
      
 69 
     | 
    
         
            +
                      raise "Unhandled param type #{par["type"]}"
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  osym = oname.downcase.to_sym
         
     | 
| 
      
 74 
     | 
    
         
            +
                  define_method(osym) do |*args|
         
     | 
| 
      
 75 
     | 
    
         
            +
                    logger.debug "called #{osym} with #{args.inspect}"
         
     | 
| 
      
 76 
     | 
    
         
            +
                    if check_arg_count && args.size != param_handlers.size
         
     | 
| 
      
 77 
     | 
    
         
            +
                      raise ArgumentError, "expected #{param_handlers.size} arguments, got #{args.size}"
         
     | 
| 
      
 78 
     | 
    
         
            +
                    end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                    bytes = u8(ovalue)
         
     | 
| 
      
 81 
     | 
    
         
            +
                    bytes += param_simple(cvalue) unless cvalue.nil?
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
                    bytes += args.zip(param_handlers).map do |a, h|
         
     | 
| 
      
 84 
     | 
    
         
            +
                      h ||= ->(x) { param_simple(x) }
         
     | 
| 
      
 85 
     | 
    
         
            +
                      # h.call(a) would have self = Op instead of #<Op>
         
     | 
| 
      
 86 
     | 
    
         
            +
                      instance_exec(a, &h)
         
     | 
| 
      
 87 
     | 
    
         
            +
                    end.join("")
         
     | 
| 
      
 88 
     | 
    
         
            +
                    logger.debug "returning bytecode: #{bytes.inspect}"
         
     | 
| 
      
 89 
     | 
    
         
            +
                    bytes
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                rescue TODO
         
     | 
| 
      
 92 
     | 
    
         
            +
                  logger.debug "Could not define #{oname}"
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                @loaded = false
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                def self.load_yml
         
     | 
| 
      
 98 
     | 
    
         
            +
                  return if @loaded
         
     | 
| 
      
 99 
     | 
    
         
            +
                  fname = File.expand_path("../../../data/ev3.yml", __FILE__)
         
     | 
| 
      
 100 
     | 
    
         
            +
                  yml = YAML.load_file(fname)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  op_hash = yml["ops"]
         
     | 
| 
      
 102 
     | 
    
         
            +
                  op_hash.each do |oname, odata|
         
     | 
| 
      
 103 
     | 
    
         
            +
                    load_op(oname, odata)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                  defines = yml["defines"]
         
     | 
| 
      
 107 
     | 
    
         
            +
                  defines.each do |dname, ddata|
         
     | 
| 
      
 108 
     | 
    
         
            +
                    load_const(dname, ddata["value"])
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
                  enums = yml["enums"]
         
     | 
| 
      
 112 
     | 
    
         
            +
                  enums.each do |ename, edata|
         
     | 
| 
      
 113 
     | 
    
         
            +
                    edata["members"].each do |mname, mdata|
         
     | 
| 
      
 114 
     | 
    
         
            +
                      load_const(mname, mdata["value"])
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                  @loaded = true
         
     | 
| 
      
 119 
     | 
    
         
            +
                end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
                def initialize(globals = nil, locals = nil)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  self.class.load_yml
         
     | 
| 
      
 123 
     | 
    
         
            +
                  @globals = globals
         
     | 
| 
      
 124 
     | 
    
         
            +
                  @locals = locals
         
     | 
| 
      
 125 
     | 
    
         
            +
                end
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                private
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                PRIMPAR_SHORT      = 0x00
         
     | 
| 
      
 130 
     | 
    
         
            +
                PRIMPAR_LONG       = 0x80
         
     | 
| 
      
 131 
     | 
    
         
            +
             
     | 
| 
      
 132 
     | 
    
         
            +
                PRIMPAR_CONST      = 0x00
         
     | 
| 
      
 133 
     | 
    
         
            +
                PRIMPAR_VARIABEL   = 0x40
         
     | 
| 
      
 134 
     | 
    
         
            +
                PRIMPAR_LOCAL      = 0x00
         
     | 
| 
      
 135 
     | 
    
         
            +
                PRIMPAR_GLOBAL     = 0x20
         
     | 
| 
      
 136 
     | 
    
         
            +
                PRIMPAR_HANDLE     = 0x10
         
     | 
| 
      
 137 
     | 
    
         
            +
                PRIMPAR_ADDR       = 0x08
         
     | 
| 
      
 138 
     | 
    
         
            +
             
     | 
| 
      
 139 
     | 
    
         
            +
                PRIMPAR_INDEX      = 0x1F
         
     | 
| 
      
 140 
     | 
    
         
            +
                PRIMPAR_CONST_SIGN = 0x20
         
     | 
| 
      
 141 
     | 
    
         
            +
                PRIMPAR_VALUE      = 0x3F
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                PRIMPAR_BYTES      = 0x07
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                PRIMPAR_STRING_OLD = 0
         
     | 
| 
      
 146 
     | 
    
         
            +
                PRIMPAR_1_BYTE     = 1
         
     | 
| 
      
 147 
     | 
    
         
            +
                PRIMPAR_2_BYTES    = 2
         
     | 
| 
      
 148 
     | 
    
         
            +
                PRIMPAR_4_BYTES    = 3
         
     | 
| 
      
 149 
     | 
    
         
            +
                PRIMPAR_STRING     = 4
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                PRIMPAR_LABEL      = 0x20
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                def make_lcn(n, bytes)
         
     | 
| 
      
 154 
     | 
    
         
            +
                  case bytes
         
     | 
| 
      
 155 
     | 
    
         
            +
                  when 0
         
     | 
| 
      
 156 
     | 
    
         
            +
                    [n & PRIMPAR_VALUE]
         
     | 
| 
      
 157 
     | 
    
         
            +
                  when 1
         
     | 
| 
      
 158 
     | 
    
         
            +
                    [PRIMPAR_LONG | PRIMPAR_1_BYTE, n & 0xff]
         
     | 
| 
      
 159 
     | 
    
         
            +
                  when 2
         
     | 
| 
      
 160 
     | 
    
         
            +
                    [PRIMPAR_LONG | PRIMPAR_2_BYTES, n & 0xff, (n >> 8) & 0xff]
         
     | 
| 
      
 161 
     | 
    
         
            +
                  else
         
     | 
| 
      
 162 
     | 
    
         
            +
                    [PRIMPAR_LONG | PRIMPAR_4_BYTES,
         
     | 
| 
      
 163 
     | 
    
         
            +
                     n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]
         
     | 
| 
      
 164 
     | 
    
         
            +
                  end
         
     | 
| 
      
 165 
     | 
    
         
            +
                end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def make_lc(n, bytes = nil)
         
     | 
| 
      
 168 
     | 
    
         
            +
                  bytes ||= if (-31 .. 31).include? n
         
     | 
| 
      
 169 
     | 
    
         
            +
                              0
         
     | 
| 
      
 170 
     | 
    
         
            +
                            elsif (-127 .. 127).include? n
         
     | 
| 
      
 171 
     | 
    
         
            +
                              1
         
     | 
| 
      
 172 
     | 
    
         
            +
                            elsif (-32767 .. 32767).include? n
         
     | 
| 
      
 173 
     | 
    
         
            +
                              2
         
     | 
| 
      
 174 
     | 
    
         
            +
                            else
         
     | 
| 
      
 175 
     | 
    
         
            +
                              4
         
     | 
| 
      
 176 
     | 
    
         
            +
                            end
         
     | 
| 
      
 177 
     | 
    
         
            +
                  make_lcn(n, bytes)
         
     | 
| 
      
 178 
     | 
    
         
            +
                end
         
     | 
| 
      
 179 
     | 
    
         
            +
             
     | 
| 
      
 180 
     | 
    
         
            +
                def make_v(n, local_or_global)
         
     | 
| 
      
 181 
     | 
    
         
            +
                  vartag = PRIMPAR_VARIABEL | local_or_global
         
     | 
| 
      
 182 
     | 
    
         
            +
                  if (0 .. 31).include? n
         
     | 
| 
      
 183 
     | 
    
         
            +
                    return [vartag | (n & PRIMPAR_VALUE)]
         
     | 
| 
      
 184 
     | 
    
         
            +
                  elsif (0 .. 255).include? n
         
     | 
| 
      
 185 
     | 
    
         
            +
                    return [vartag | PRIMPAR_LONG | PRIMPAR_1_BYTE, n & 0xff]
         
     | 
| 
      
 186 
     | 
    
         
            +
                  elsif (0 .. 65535).include? n
         
     | 
| 
      
 187 
     | 
    
         
            +
                    return [vartag | PRIMPAR_LONG | PRIMPAR_2_BYTES, n & 0xff, (n >> 8) & 0xff]
         
     | 
| 
      
 188 
     | 
    
         
            +
                  end
         
     | 
| 
      
 189 
     | 
    
         
            +
                  [vartag | PRIMPAR_LONG | PRIMPAR_4_BYTES,
         
     | 
| 
      
 190 
     | 
    
         
            +
                   n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]
         
     | 
| 
      
 191 
     | 
    
         
            +
                end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                def make_var(sym)
         
     | 
| 
      
 194 
     | 
    
         
            +
                  raise "No variables declared, cannot process symbols" if @locals.nil? && @globals.nil?
         
     | 
| 
      
 195 
     | 
    
         
            +
                  if @locals.key?(sym)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    o = @locals.offset(sym)
         
     | 
| 
      
 197 
     | 
    
         
            +
                    make_v(o, PRIMPAR_LOCAL)
         
     | 
| 
      
 198 
     | 
    
         
            +
                  elsif @globals.key?(sym)
         
     | 
| 
      
 199 
     | 
    
         
            +
                    o = @globals.offset(sym)
         
     | 
| 
      
 200 
     | 
    
         
            +
                    make_v(o, PRIMPAR_GLOBAL)
         
     | 
| 
      
 201 
     | 
    
         
            +
                  else
         
     | 
| 
      
 202 
     | 
    
         
            +
                    raise "Variable #{sym} not found"
         
     | 
| 
      
 203 
     | 
    
         
            +
                  end
         
     | 
| 
      
 204 
     | 
    
         
            +
                end
         
     | 
| 
      
 205 
     | 
    
         
            +
             
     | 
| 
      
 206 
     | 
    
         
            +
                # @return [ByteString]
         
     | 
| 
      
 207 
     | 
    
         
            +
                def param_simple(x)
         
     | 
| 
      
 208 
     | 
    
         
            +
                  case x
         
     | 
| 
      
 209 
     | 
    
         
            +
                  when Integer
         
     | 
| 
      
 210 
     | 
    
         
            +
                    make_lc(x).map(&:chr).join("")
         
     | 
| 
      
 211 
     | 
    
         
            +
                  when Complex
         
     | 
| 
      
 212 
     | 
    
         
            +
                    make_lc(x.real, x.imag).map(&:chr).join("")
         
     | 
| 
      
 213 
     | 
    
         
            +
                  when String
         
     | 
| 
      
 214 
     | 
    
         
            +
                    u8(0x80) + x + u8(0x00)
         
     | 
| 
      
 215 
     | 
    
         
            +
                  when Float
         
     | 
| 
      
 216 
     | 
    
         
            +
                    u8(0x83) + f32(x)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  when Symbol
         
     | 
| 
      
 218 
     | 
    
         
            +
                    make_var(x).map(&:chr).join("")
         
     | 
| 
      
 219 
     | 
    
         
            +
                  else
         
     | 
| 
      
 220 
     | 
    
         
            +
                    raise ArgumentError, "Unexpected type: #{x.class}"
         
     | 
| 
      
 221 
     | 
    
         
            +
                  end
         
     | 
| 
      
 222 
     | 
    
         
            +
                end
         
     | 
| 
      
 223 
     | 
    
         
            +
              end
         
     | 
| 
      
 224 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Lignite
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Part of an assembled RBF file
         
     | 
| 
      
 3 
     | 
    
         
            +
              class RbfObject
         
     | 
| 
      
 4 
     | 
    
         
            +
                include Bytes
         
     | 
| 
      
 5 
     | 
    
         
            +
                def self.vmthread(body:, local_bytes:)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  new(owner: 0, triggers: 0, local_bytes: local_bytes, body: body)
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def self.subcall(body:, local_bytes:)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  new(owner: 0, triggers: 1, local_bytes: local_bytes, body: body)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def self.block(owner:, triggers:, body:)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  new(owner: owner, triggers: triggers, local_bytes: 0, body: body)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def initialize(owner:, triggers:, local_bytes:, body:)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @owner = owner
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @triggers = triggers
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @local_bytes = local_bytes
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @body = body
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def header(pos_before_header = 0)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  u32(pos_before_header + 12) + # size of header
         
     | 
| 
      
 26 
     | 
    
         
            +
                    u16(@owner) + u16(@triggers) + u32(@local_bytes)
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def body
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @body
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,103 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Lignite
         
     | 
| 
      
 2 
     | 
    
         
            +
              class SystemCommands
         
     | 
| 
      
 3 
     | 
    
         
            +
                include Bytes
         
     | 
| 
      
 4 
     | 
    
         
            +
                include Logger
         
     | 
| 
      
 5 
     | 
    
         
            +
                extend Logger
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # @param conn [Connection]
         
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(conn = Connection.create)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @message_sender = MessageSender.new(conn)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  load_yml
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def load_yml
         
     | 
| 
      
 14 
     | 
    
         
            +
                  fname = File.expand_path("../../../data/sysops.yml", __FILE__)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  op_hash = YAML.load_file(fname)["sysops"]
         
     | 
| 
      
 16 
     | 
    
         
            +
                  op_hash.each do |oname, odata|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    load_op(oname, odata)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                # oname LIST_FILES
         
     | 
| 
      
 22 
     | 
    
         
            +
                def load_op(oname, odata)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ovalue = odata["value"]
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  param_handlers, return_handlers = handlers(odata)
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  osym = oname.downcase.to_sym
         
     | 
| 
      
 28 
     | 
    
         
            +
                  self.class.send(:define_method, osym) do |*args|
         
     | 
| 
      
 29 
     | 
    
         
            +
                    logger.debug "called #{osym} with #{args.inspect}"
         
     | 
| 
      
 30 
     | 
    
         
            +
                    if args.size != param_handlers.size
         
     | 
| 
      
 31 
     | 
    
         
            +
                      raise ArgumentError, "expected #{param_handlers.size} arguments, got #{args.size}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                    bytes = u8(ovalue)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    bytes += param_handlers.zip(args).map do |h, a|
         
     | 
| 
      
 36 
     | 
    
         
            +
                      # h.call(a) would have self = Op instead of #<Op>
         
     | 
| 
      
 37 
     | 
    
         
            +
                      instance_exec(a, &h)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end.join("")
         
     | 
| 
      
 39 
     | 
    
         
            +
                    logger.debug "sysop to execute: #{bytes.inspect}"
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    reply = @message_sender.system_command_with_reply(bytes)
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    # TODO: parse it with return_handlers
         
     | 
| 
      
 44 
     | 
    
         
            +
                    replies = return_handlers.map do |h|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      parsed, reply = h.call(reply)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      parsed
         
     | 
| 
      
 47 
     | 
    
         
            +
                    end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    raise "Unparsed reply #{reply.inspect}" unless reply.empty?
         
     | 
| 
      
 49 
     | 
    
         
            +
                    # A single reply is returned as a scalar, not an array
         
     | 
| 
      
 50 
     | 
    
         
            +
                    replies.size == 1 ? replies.first : replies
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                def handlers(odata)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  oparams = odata["params"]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  param_handlers = []
         
     | 
| 
      
 57 
     | 
    
         
            +
                  return_handlers = []
         
     | 
| 
      
 58 
     | 
    
         
            +
                  oparams.each do |p|
         
     | 
| 
      
 59 
     | 
    
         
            +
                    if p["dir"] == "in"
         
     | 
| 
      
 60 
     | 
    
         
            +
                      param_handlers << param_handler(p)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    else
         
     | 
| 
      
 62 
     | 
    
         
            +
                      return_handlers << return_handler(p)
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  end
         
     | 
| 
      
 65 
     | 
    
         
            +
                  [param_handlers, return_handlers]
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def param_handler(oparam)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  case oparam["type"]
         
     | 
| 
      
 70 
     | 
    
         
            +
                  when "U8"
         
     | 
| 
      
 71 
     | 
    
         
            +
                    ->(x) { u8(x) }
         
     | 
| 
      
 72 
     | 
    
         
            +
                  when "U16"
         
     | 
| 
      
 73 
     | 
    
         
            +
                    ->(x) { u16(x) }
         
     | 
| 
      
 74 
     | 
    
         
            +
                  when "U32"
         
     | 
| 
      
 75 
     | 
    
         
            +
                    ->(x) { u32(x) }
         
     | 
| 
      
 76 
     | 
    
         
            +
                  when "BYTES"
         
     | 
| 
      
 77 
     | 
    
         
            +
                    ->(x) { x }
         
     | 
| 
      
 78 
     | 
    
         
            +
                  when "ZBYTES"
         
     | 
| 
      
 79 
     | 
    
         
            +
                    ->(x) { x + u8(0) }
         
     | 
| 
      
 80 
     | 
    
         
            +
                  else
         
     | 
| 
      
 81 
     | 
    
         
            +
                    raise
         
     | 
| 
      
 82 
     | 
    
         
            +
                  end
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                # the handler is a lambda returning a pair:
         
     | 
| 
      
 86 
     | 
    
         
            +
                # a parsed value and the rest of the input
         
     | 
| 
      
 87 
     | 
    
         
            +
                def return_handler(oparam)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  case oparam["type"]
         
     | 
| 
      
 89 
     | 
    
         
            +
                  when "U8"
         
     | 
| 
      
 90 
     | 
    
         
            +
                    ->(i) { [unpack_u8(i[0, 1]), i[1..-1]] }
         
     | 
| 
      
 91 
     | 
    
         
            +
                  when "U16"
         
     | 
| 
      
 92 
     | 
    
         
            +
                    ->(i) { [unpack_u16(i[0, 2]), i[2..-1]] }
         
     | 
| 
      
 93 
     | 
    
         
            +
                  when "U32"
         
     | 
| 
      
 94 
     | 
    
         
            +
                    ->(i) { [unpack_u32(i[0, 4]), i[4..-1]] }
         
     | 
| 
      
 95 
     | 
    
         
            +
                  when "BYTES"
         
     | 
| 
      
 96 
     | 
    
         
            +
                    ->(i) { [i, ""] }
         
     | 
| 
      
 97 
     | 
    
         
            +
                  else
         
     | 
| 
      
 98 
     | 
    
         
            +
                    raise
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
              end
         
     | 
| 
      
 103 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Lignite
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Allocate local or global variables
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Variables
         
     | 
| 
      
 4 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 5 
     | 
    
         
            +
                  @offset = 0
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @vars = {}
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def add(id, size)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  raise "Duplicate variable #{id}" if @vars.key?(id)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @vars[id] = {offset: @offset, size: size}
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @offset += size
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def bytesize
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @offset
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def key?(sym)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @vars.key?(sym)
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def offset(sym)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @vars[sym][:offset]
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
    
        data/lignite.gemspec
    ADDED
    
    | 
         @@ -0,0 +1,74 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- encoding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require File.expand_path(File.dirname(__FILE__) + "/lib/lignite/version")
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Gem::Specification.new do |s|
         
     | 
| 
      
 6 
     | 
    
         
            +
              s.name        = "lignite"
         
     | 
| 
      
 7 
     | 
    
         
            +
              s.version     = Lignite::VERSION
         
     | 
| 
      
 8 
     | 
    
         
            +
              s.summary     = "Program LEGO Mindstorms EV3 in Ruby"
         
     | 
| 
      
 9 
     | 
    
         
            +
              s.description = <<TXT
         
     | 
| 
      
 10 
     | 
    
         
            +
            Lignite is a set of Ruby tools to interact with LEGO Mindstorms EV3.
         
     | 
| 
      
 11 
     | 
    
         
            +
            It uses the original LMS2012 firmware, so ev3dev is not required.
         
     | 
| 
      
 12 
     | 
    
         
            +
            TXT
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              s.author      = "Martin Vidner"
         
     | 
| 
      
 15 
     | 
    
         
            +
              s.email       = "martin@vidner.net"
         
     | 
| 
      
 16 
     | 
    
         
            +
              s.homepage    = "https://github.com/mvidner/lignite"
         
     | 
| 
      
 17 
     | 
    
         
            +
              s.license     = "GPL-3.0-only"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              # ruby -e 'puts `git ls-files`.lines.map { |f| "    %s,\n" % f.strip.inspect }'
         
     | 
| 
      
 20 
     | 
    
         
            +
              s.files       = [
         
     | 
| 
      
 21 
     | 
    
         
            +
                ".gitignore",
         
     | 
| 
      
 22 
     | 
    
         
            +
                "COPYING",
         
     | 
| 
      
 23 
     | 
    
         
            +
                "Gemfile",
         
     | 
| 
      
 24 
     | 
    
         
            +
                "README.md",
         
     | 
| 
      
 25 
     | 
    
         
            +
                "Rakefile",
         
     | 
| 
      
 26 
     | 
    
         
            +
                "VERSION",
         
     | 
| 
      
 27 
     | 
    
         
            +
                "bin/ev3tool",
         
     | 
| 
      
 28 
     | 
    
         
            +
                "data/ev3.yml",
         
     | 
| 
      
 29 
     | 
    
         
            +
                "data/lignite-btaddr",
         
     | 
| 
      
 30 
     | 
    
         
            +
                "data/sysops.yml",
         
     | 
| 
      
 31 
     | 
    
         
            +
                "examples/hello.rb",
         
     | 
| 
      
 32 
     | 
    
         
            +
                "examples/lights.rb",
         
     | 
| 
      
 33 
     | 
    
         
            +
                "examples/motors.rb",
         
     | 
| 
      
 34 
     | 
    
         
            +
                "examples/sound.rb",
         
     | 
| 
      
 35 
     | 
    
         
            +
                "examples/sys_list_files.rb",
         
     | 
| 
      
 36 
     | 
    
         
            +
                "lib/lignite.rb",
         
     | 
| 
      
 37 
     | 
    
         
            +
                "lib/lignite/assembler.rb",
         
     | 
| 
      
 38 
     | 
    
         
            +
                "lib/lignite/body_compiler.rb",
         
     | 
| 
      
 39 
     | 
    
         
            +
                "lib/lignite/bytes.rb",
         
     | 
| 
      
 40 
     | 
    
         
            +
                "lib/lignite/connection.rb",
         
     | 
| 
      
 41 
     | 
    
         
            +
                "lib/lignite/connection/bluetooth.rb",
         
     | 
| 
      
 42 
     | 
    
         
            +
                "lib/lignite/connection/usb.rb",
         
     | 
| 
      
 43 
     | 
    
         
            +
                "lib/lignite/direct_commands.rb",
         
     | 
| 
      
 44 
     | 
    
         
            +
                "lib/lignite/logger.rb",
         
     | 
| 
      
 45 
     | 
    
         
            +
                "lib/lignite/message.rb",
         
     | 
| 
      
 46 
     | 
    
         
            +
                "lib/lignite/message_sender.rb",
         
     | 
| 
      
 47 
     | 
    
         
            +
                "lib/lignite/op_compiler.rb",
         
     | 
| 
      
 48 
     | 
    
         
            +
                "lib/lignite/rbf_object.rb",
         
     | 
| 
      
 49 
     | 
    
         
            +
                "lib/lignite/system_commands.rb",
         
     | 
| 
      
 50 
     | 
    
         
            +
                "lib/lignite/variables.rb",
         
     | 
| 
      
 51 
     | 
    
         
            +
                "lib/lignite/version.rb",
         
     | 
| 
      
 52 
     | 
    
         
            +
                "lignite.gemspec",
         
     | 
| 
      
 53 
     | 
    
         
            +
                "spec/assembler_spec.rb",
         
     | 
| 
      
 54 
     | 
    
         
            +
                "spec/data/HelloWorld-subop.rb",
         
     | 
| 
      
 55 
     | 
    
         
            +
                "spec/data/HelloWorld-subop.rbf",
         
     | 
| 
      
 56 
     | 
    
         
            +
                "spec/data/HelloWorld.lms",
         
     | 
| 
      
 57 
     | 
    
         
            +
                "spec/data/HelloWorld.rb",
         
     | 
| 
      
 58 
     | 
    
         
            +
                "spec/data/HelloWorld.rbf",
         
     | 
| 
      
 59 
     | 
    
         
            +
                "spec/data/VernierReadout.lms",
         
     | 
| 
      
 60 
     | 
    
         
            +
                "spec/data/VernierReadout.rb",
         
     | 
| 
      
 61 
     | 
    
         
            +
                "spec/data/VernierReadout.rbf",
         
     | 
| 
      
 62 
     | 
    
         
            +
                "spec/spec_helper.rb"
         
     | 
| 
      
 63 
     | 
    
         
            +
              ]
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              s.executables = s.files.grep(/^bin\//) { |f| File.basename(f) }
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              s.required_ruby_version = ">= 2.1" # mandatory keyword arguments
         
     | 
| 
      
 68 
     | 
    
         
            +
              s.add_dependency "libusb", "~> 0.6"
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
              s.add_development_dependency "coveralls", "~> 0"
         
     | 
| 
      
 71 
     | 
    
         
            +
              s.add_development_dependency "simplecov", "~> 0"
         
     | 
| 
      
 72 
     | 
    
         
            +
              s.add_development_dependency "rspec", "~> 3"
         
     | 
| 
      
 73 
     | 
    
         
            +
              s.add_development_dependency "yard", "~> 0"
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     |