Class: ArduinoSketch

Inherits:
Object
  • Object
show all
Includes:
ExternalVariableProcessing
Defined in:
lib/rad/arduino_sketch.rb,
lib/rad/sim/arduino_sketch.rb

Overview

def blink_it

    blink 13, 500
  end

end

added pin methods for servos and latching which generate an array of structs to contain setup and status
input_pin 12, :as => :back_off_button, :latch => :off
input_pin 8, :as => :red_button, :latch => :off # adjust is optional with default set to 200

added add_to_setup method that takes a string of c code and adds it to setup
colons are options and will be added if not present  
no translation from ruby for now

example:

add_to_setup "call_my_new_method();", "call_another();"  

added some checking to c translation that (hopefully) makes it a bit more predictable
most notably, we keep track of all external variables and let the translator know they exist

Constant Summary collapse

@@twowire_inc =

find another way to do this

FALSE
@@hwserial_inc =
FALSE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ExternalVariableProcessing

#c_type, #check_variable_type, #post_process_arrays, #post_process_vars, #pre_process_vars, #process_external_vars, #translate_variables

Constructor Details

#initializeArduinoSketch

:nodoc:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/rad/arduino_sketch.rb', line 164

def initialize #:nodoc:
  @servo_settings = [] # need modular way to add this
  @debounce_settings = [] # need modular way to add this
  @hysteresis_settings = []
  @spectra_settings = []
  @servo_pins = [] 
  @debounce_pins = []
  @hysteresis_pins = []
  @spectra_pins = []
  $external_array_vars = [] 
  $external_vars =[]
  $external_var_identifiers = []
  $sketch_methods = []
  $load_libraries ||= []
  $defines  ||= []
  $define_types = {}
  $array_types = {}
  $array_index_helpers ||= ('a'..'zzz').to_a

  @declarations = []
  @pin_modes = {:output => [], :input => []}
  @pullups = []
  @other_setup = [] # specifically, Serial.begin
  @assembler_declarations = []
  @accessors = []
  @signatures = ["int main();"]
 
  helper_methods = []
  @helper_methods = helper_methods.join( "\n" )

end

Instance Attribute Details

#pinsObject

Returns the value of attribute pins.



7
8
9
# File 'lib/rad/sim/arduino_sketch.rb', line 7

def pins
  @pins
end

Class Method Details

.add_to_setup(meth) ⇒ Object



600
601
602
603
# File 'lib/rad/arduino_sketch.rb', line 600

def self.add_to_setup(meth) 
  meth = meth.gsub("setup", "additional_setup")
  post_process_ruby_to_c_methods(meth)
end

.output_pin(num, opts) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/rad/sim/arduino_sketch.rb', line 13

def self.output_pin(num, opts)
  module_eval "@pins ||= []"
  module_eval do 
    @pins <<  Pin.new( num, :type => :output )
  end

  if opts[:as]
     module_eval <<-CODE
       def #{opts[:as]}
         pins.select{|p| p.num == #{num}}.first
       end
     CODE
   end
end

.post_process_ruby_to_c_methods(e) ⇒ Object



605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/rad/arduino_sketch.rb', line 605

def self.post_process_ruby_to_c_methods(e)      
  clean_c_methods = []
    # need to take a look at the \(unsigned in the line below not sure if we are really trying to catch something like that
    if e !~ /^\s*(#{C_VAR_TYPES})(\W{1,6}|\(unsigned\()(#{$external_var_identifiers.join("|")})/ || $external_var_identifiers.empty?
      # use the list of identifers the external_vars method of the sketch and remove the parens the ruby2c sometime adds to variables
      # keep an eye on the gsub!.. are we getting nil errors
      # and more recently, the \b
      e.gsub!(/\b((#{$external_var_identifiers.join("|")})\(\))/, '\2')  unless $external_var_identifiers.empty?
      clean_c_methods << e
    end
    return clean_c_methods.join( "\n" )
end

.pre_process(sketch_string) ⇒ Object

:nodoc:



585
586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/rad/arduino_sketch.rb', line 585

def self.pre_process(sketch_string) #:nodoc:
  result = sketch_string 
  # add external vars to each method (needed for better translation, will be removed in make:upload)
  result.gsub!(/(^\s*def\s.\w*(\(.*\))?)/, '\1' + " \n #{$external_vars.join("  \n ")}"  )
  # gather method names
  sketch_methods = result.scan(/^\s*def\s.\w*/)
  sketch_methods.each {|m| $sketch_methods << m.gsub(/\s*def\s*/, "") }
  
  result.gsub!("HIGH", "1")
  result.gsub!("LOW", "0")
  result.gsub!("ON", "1")
  result.gsub!("OFF", "0")
  result
end

Instance Method Details

#add(st) ⇒ Object

:nodoc:



399
400
401
# File 'lib/rad/arduino_sketch.rb', line 399

def add(st) #:nodoc:
  @helper_methods << "\n#{st}\n"
end

#array(arg) ⇒ Object

array “char buffer” result: char buffer; array “char buffer” result: char buffer; todo need to feed array external array identifiers to rtc if they are in plugins or libraries, (not so sure about this will do more testing)



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rad/arduino_sketch.rb', line 204

def array(arg)
  if arg
      arg = arg.chomp.rstrip.lstrip
      arg.sub!("@","__")
      name = arg.scan(/\s*(\w*)\[\d*\]?/).first.first
      # help rad_processor do a better job with array types
      types = ["int", "long", "char*", "unsigned int", "unsigned long", "byte", "bool", "float" ]
      types.each_with_index do |type, i|
        @type = types[i] if /#{type}/ =~ arg
      end
      raise ArgumentError, "type not currently supported.. got: #{arg}.  Currently supporting #{types.join(", ")}" unless @type

      arg = "#{arg};" unless arg[-1,1] == ";"
      $array_types[name] = @type
      @type = nil
      $external_var_identifiers << name unless $external_var_identifiers.include?(name)
      # add array_name declaration
      $external_array_vars << arg unless $external_array_vars.include?(arg)
  end
end

#assembler(name, signature, code) ⇒ Object

Write inline assembler code. ‘Name’ is a symbol representing the name of the function to be defined in the assembly code; ‘signature’ is the function signature for the function being defined; and ‘code’ is the assembly code itself (both of these last two arguments are strings). See an example here: rad.rubyforge.org/examples/assembler_test.html



569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/rad/arduino_sketch.rb', line 569

def assembler(name, signature, code)
  @assembler_declarations << signature
  assembler_code = <<-CODE
    .file "#{name}.S"
    .arch #{Makefile.hardware_params['mcu']}
    .global __do_copy_data
    .global __do_clear_bss
    .text
  .global #{name}
    .type #{name}, @function
  #{code}
  CODE
          
  File.open(File.expand_path("#{RAD_ROOT}") + "/#{PROJECT_DIR_NAME}/#{name}.S", "w"){|f| f << assembler_code}
end

#comment_box(content) ⇒ Object

:nodoc:



619
620
621
622
623
624
625
626
# File 'lib/rad/arduino_sketch.rb', line 619

def comment_box( content ) #:nodoc:
  out = []
  out << "/" * 74
  out << "// " + content
  out << "/" * 74
  
  return out.join( "\n" )
end

#compose_setupObject

:nodoc: also composes headers and signatures



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/rad/arduino_sketch.rb', line 436

def compose_setup #:nodoc: also composes headers and signatures

  software_params = Makefile::software_params
  declarations = []
  plugin_directives = []
  signatures = []
  external_vars = []
  setup = []
  additional_setup =[]
  helpers = []
  main = []
  result = []
  
  declarations << comment_box( "Auto-generated by RAD" )
  
  declarations << "#include <WProgram.h>\n"
  declarations << "#include <SoftwareSerial.h>\n" unless software_params['experimental'] == true
  $load_libraries.each { |lib| declarations << "#include <#{lib}.h>" } unless $load_libraries.nil?
  $defines.each { |d| declarations << d }

  plugin_directives << comment_box( 'plugin directives' )
  $plugin_directives.each {|dir| plugin_directives << dir } unless $plugin_directives.nil? ||  $plugin_directives.empty?
  
  signatures << comment_box( 'method signatures' )
  signatures << "void loop();"
  signatures << "void setup();"
  signatures << "// sketch signatures"
  @signatures.each {|sig| signatures << sig}
  signatures << "// plugin signatures"
  $plugin_signatures.each {|sig| signatures << sig } unless $plugin_signatures.nil? || $plugin_signatures.empty?
  external_vars << "\n" + comment_box( "plugin external variables" )
  $plugin_external_variables.each { |meth| external_vars << meth } unless $plugin_external_variables.nil? || $plugin_external_variables.empty?
  
  signatures << "\n" + comment_box( "plugin structs" )
  $plugin_structs.each { |k,v| signatures << v } unless $plugin_structs.nil? || $plugin_structs.empty?

  external_vars << "\n" + comment_box( "sketch external variables" )
  
  $external_vars.each {|v| external_vars << v }
  external_vars << "" 
  external_vars << "// servo_settings array"

  array_size = @servo_settings.empty? ? 1 : @servo_pins.max + 1 # conserve space if no variables needed
  external_vars << "struct servo serv[#{array_size}] = { #{@servo_settings.join(", ")} };" if $plugin_structs[:servo]
  external_vars << "" 

  external_vars << "// debounce array"
  array_size = @debounce_settings.empty? ? 1 : @debounce_pins.max + 1 # conserve space if no variables needed
  external_vars << "struct debounce dbce[#{array_size}] = { #{@debounce_settings.join(", ")} };" if $plugin_structs[:debounce]
  external_vars << ""
  
  external_vars << "// hysteresis array"
  h_array_size = @hysteresis_settings.empty? ? 1 : @hysteresis_pins.length + 1 # conserve space if no variables needed
  external_vars << "struct hysteresis hyst[#{h_array_size}] = { #{@hysteresis_settings.join(", ")} };" if $plugin_structs[:sensor]
  external_vars << ""
  
  external_vars << "// spectrasymbol soft pot array"
  sp_array_size = @spectra_settings.empty? ? 1 : @spectra_pins.length + 1 # conserve space if no variables needed
  external_vars << "struct spectra spec[#{sp_array_size}] = { #{@spectra_settings.join(", ")} };" if $plugin_structs[:spectra]
  external_vars << ""
  
  $external_array_vars.each { |var| external_vars << var } if $external_array_vars

  external_vars << "\n" + comment_box( "variable and accessors" )
  @declarations.each {|dec| external_vars << dec}
  external_vars << ""     
  @accessors.each {|ac| external_vars << ac}

  # fix naming
  external_vars << "\n" + comment_box( "assembler declarations" )
  unless @assembler_declarations.empty?
    external_vars << <<-CODE
       extern "C" {
         #{@assembler_declarations.join("\n")}
       }
    CODE
  end

  external_vars << "\n" + comment_box( "setup" )
  setup << "void setup() {"
  setup << "\t// pin modes"
  
  @pin_modes.each do |k,v|
    v.each do |value| 
      setup << "\tpinMode(#{value}, #{k.to_s.upcase});"
    end
  end

  @pullups.each do |pin|
   setup << "\tdigitalWrite( #{pin}, HIGH ); // enable pull-up resistor for input"
  end
  
  unless $add_to_setup.nil? || $add_to_setup.empty?
    setup << "\t// setup from plugins via add_to_setup method"
    $add_to_setup.each {|item| setup << "\t#{item}"}
  end
  
  unless @other_setup.empty?
    setup << "\t// other setup"
    setup << @other_setup.join( "\n" )
  end
  
  additional_setup << "}\n"
  
  helpers << comment_box( "helper methods" )
  helpers << "\n// RAD built-in helpers"
  helpers << @helper_methods.lstrip
  
  helpers << "\n" + comment_box( "plugin methods" )  
  # need to add plugin name to this... 
  $plugin_methods.each { |meth| helpers << "#{meth[0][0]}\n" } unless $plugin_methods.nil? || $plugin_methods.empty?
  
  if @@hwserial_inc == TRUE
    helpers << "\n// serial helpers"
    helpers << serial_boilerplate.lstrip
  end
  
  main << "\n" + comment_box( "main() function" )
  main << "int main() {"
  main << "\tinit();"
  main << "\tsetup();"
  main << "\tfor( ;; ) { loop(); }"
  main << "\treturn 0;"
  main << "}"

  main << "\n" + comment_box( "loop!  Autogenerated by RubyToC, sorry it's ugly." )

return [declarations, plugin_directives, signatures, external_vars, setup, additional_setup, helpers, main]

end

#define(arg) ⇒ Object

define “DS1307_SEC 0” result: #define DS1307_SEC 0 note we send the constant identifiers and type to our rad_type_checker however, it only knows about long, float, str.… so we don’t send ints …yet.. need more testing



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/rad/arduino_sketch.rb', line 231

def define(arg)
  if arg
      arg = arg.chomp.rstrip.lstrip
      name = arg.split(" ").first
      value = arg.gsub!("#{name} ","")
      # negative
      if value =~ /^-(\d|x)*$/ 
         type = "long"
       # negative float
       elsif value =~ /^-(\d|\.|x)*$/ 
         type = "float" 
       elsif value =~ /[a-zA-Z]/
         type = "str"
         value = "\"#{value}\""
       elsif value !~ /(\.|x)/
         type = "long"
       elsif value =~ /(\d*\.\d*)/ # and no 
         type = "float"
       elsif value =~ /0x\d\d/
         type = "byte"
       else 
         raise ArgumentError, "opps, could not determine the define type, got #{value}"
       end
      $define_types[name] = type
      arg = "#define #{name} #{value}"
      $defines << arg
      dummy_for_testing = arg, type
  end
end

#delay(millis) ⇒ Object



36
37
# File 'lib/rad/sim/arduino_sketch.rb', line 36

def delay( millis )
end

#digitalWrite(pin, value) ⇒ Object



31
32
33
34
# File 'lib/rad/sim/arduino_sketch.rb', line 31

def digitalWrite( pin, value )
  to_change = pins.select{|p| p.num == pin.num}.first
  to_change.value = value
end

#formatted_print(opts = {}) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/rad/arduino_sketch.rb', line 416

def formatted_print(opts={})

  buffer_size = opts[:buffer_size] ? opts[:buffer_size] : 64
  
  if opts[:as]
    @@sprintf_inc ||= FALSE
    if @@sprintf_inc == FALSE
      @@sprintf_inc = TRUE
      accessor = []
      accessor << "\n#undef int\n#include <stdio.h>"
      accessor << "#define write_line(...) sprintf(#{opts[:as]},__VA_ARGS__);"
      @accessors << accessor.join( "\n" )
      array("char #{opts[:as]}[#{buffer_size}]") 
    end
  end
end

#input_pin(num, opts = {}) ⇒ Object

Configure a single pin for input and setup a method to refer to that pin, i.e.:

input_pin 3, :as => :button

would configure pin 3 as an input and let you refer to it from the then on by calling the ‘button` method in your loop like so:

def loop
  digital_write led if digital_read button
end

Raises:

  • (ArgumentError)


351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/rad/arduino_sketch.rb', line 351

def input_pin(num, opts={})
  raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum)
  @pin_modes[:input] << num
  if opts[:as]
    # transitioning to :device => :button syntax
    if opts[:latch] || opts[:device] == :button
      if opts[:device] == :button
        opts[:latch] ||= :off
      end
      # add debounce settings to dbce struct array
      ArduinoPlugin.add_debounce_struct
      @debounce_pins << num
      state = opts[:latch] == :on ? 1 : 0
      prev = opts[:latch] == :on ? 0 : 1
      adjust = opts[:adjust] ? opts[:adjust] : 200
      @debounce_settings <<  "dbce[#{num}].state = #{state}, dbce[#{num}].read = 0, dbce[#{num}].prev = #{prev}, dbce[#{num}].time = 0, dbce[#{num}].adjust = #{adjust}"
    end
    if opts[:device] == :sensor
      ArduinoPlugin.add_sensor_struct
      count = @hysteresis_pins.length
      @hysteresis_pins << num
      @hysteresis_settings << "hyst[#{count}].pin = #{num}, hyst[#{count}].state = 0"
    end
    if opts[:device] == :spectra
      ArduinoPlugin.add_spectra_struct
      count = @spectra_pins.length
      @spectra_pins << num
      @spectra_settings << "spec[#{count}].pin = #{num}, spec[#{count}].state = 10, spec[#{count}].r1 = 0, spec[#{count}].r2 = 0, spec[#{count}].r3 = 0"
    end
    @declarations << "int _#{opts[ :as ]} = #{num};"

    accessor = []
    accessor << "int #{opts[ :as ]}() {"
    accessor << "\treturn _#{opts[ :as ]};"
    accessor << "}"
    @accessors << accessor.join( "\n" )
    
    @signatures << "int #{opts[ :as ]}();"
  end
  @pullups << num if opts[:as]
end

#input_pins(nums) ⇒ Object

Like ArduinoSketch#input_pin but configure more than one input pin simultaneously. Takes an array of pin numbers.



394
395
396
397
# File 'lib/rad/arduino_sketch.rb', line 394

def input_pins(nums)
  ar = Array(nums)
  ar.each {|n| input_pin(n)} 
end

#loopObject



28
29
# File 'lib/rad/sim/arduino_sketch.rb', line 28

def loop    
end

#output_pin(num, opts = {}) ⇒ Object

Configure a single pin for output and setup a method to refer to that pin, i.e.:

output_pin 7, :as => :led

would configure pin 7 as an output and let you refer to it from the then on by calling the ‘led` method in your loop like so:

def loop
  digital_write led, ON
end

Raises:

  • (ArgumentError)


272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/rad/arduino_sketch.rb', line 272

def output_pin(num, opts={})
  raise ArgumentError, "can only define pin from Fixnum, got #{num.class}" unless num.is_a?(Fixnum)
  @pin_modes[:output] << num
  if opts[:as]
    if opts[:device]
      case opts[:device]
      when :servo
        servo_setup(num, opts)
        return # don't use declarations, accessor, signatures below
      when :pa_lcd || :pa_LCD
        pa_lcd_setup(num, opts)
        return 
      when :sf_lcd || :sf_LCD
        sf_lcd_setup(num, opts)
        return         
      when :freq_out || :freq_gen || :frequency_generator
        frequency_timer(num, opts)
        return
      when :i2c
        two_wire(num, opts) unless @@twowire_inc
        return #
      when :i2c_eeprom
        two_wire(num, opts) unless @@twowire_inc
        i2c_eeprom(num, opts)
        return #
      when :i2c_ds1307
        two_wire(num, opts) unless @@twowire_inc
        ds1307(num, opts) 
        return #
      when :i2c_blinkm
        two_wire(num, opts) unless @@twowire_inc
        blinkm
        return #
      when :onewire
        one_wire(num, opts)
        return #
      when :ethernet
        ethernet(num, opts)
        return #
      else
        raise ArgumentError, "today's device choices are: :servo, :pa_lcd, :sf_lcd, :freq_out,:i2c, :i2c_eeprom, :i2c_ds1307, and :i2c_blinkm  got #{opts[:device]}"
      end
    end
    
# add state variables for outputs with :state => :on or :off -- useful for toggling a light with output_toggle -- need to make this more modular
    if opts[:state] 
      # add debounce settings to dbce struct array
      ArduinoPlugin.add_debounce_struct
      @debounce_pins << num
      state = opts[:latch] == :on ? 1 : 0
      prev = opts[:latch] == :on ? 0 : 1
      adjust = opts[:adjust] ? opts[:adjust] : 20
      @debounce_settings <<  "dbce[#{num}].state = #{state}, dbce[#{num}].read = 0, dbce[#{num}].prev = #{prev}, dbce[#{num}].time = 0, dbce[#{num}].adjust = #{adjust}"
    end
    
    @declarations << "int _#{opts[ :as ]} = #{num};"
    
    accessor = []
    accessor << "int #{opts[ :as ]}() {"
    accessor << "\treturn _#{opts[ :as ]};"
    accessor << "}"
    @accessors << accessor.join( "\n" )
    
    @signatures << "int #{opts[ :as ]}();"
  end
end

#serial_begin(opts = {}) ⇒ Object

Configure Arduino for serial communication. Optionally, set the baud rate:

serial_begin :rate => 2400

default is 9600. See www.arduino.cc/en/Serial/Begin for more details.



409
410
411
412
413
# File 'lib/rad/arduino_sketch.rb', line 409

def serial_begin(opts={})
  rate = opts[:rate] ? opts[:rate] : 9600
  @other_setup << "Serial.begin(#{rate});"
  @@hwserial_inc = TRUE
end