Class: DenkoCLI::Generator

Inherits:
Object
  • Object
show all
Includes:
Helper
Defined in:
lib/denko_cli/targets.rb,
lib/denko_cli/packages.rb,
lib/denko_cli/generator.rb

Constant Summary collapse

STANDARD_PACKAGES =
PACKAGES.each_key.map do |package|
  package unless PACKAGES[package][:only]
end.compact
TARGETS =
{
  # Core is core.
  core: [:core],

  # Specific features for the old mega168 chips.
  mega168: [:core, :one_wire, :tone, :i2c, :spi, :servo],

  # Other ATmega chips do everything.
  # Add bit bang serial for 328p / UNO since it has no extra hardware UART.
  mega: STANDARD_PACKAGES + [:uart_bb],

  # No tone, infrared or EEPROM on SAM3X / Due.
  sam3x: STANDARD_PACKAGES - [:tone, :ir_out, :eeprom],
  
  # No EEPROM on SAMD / Zero.
  samd: STANDARD_PACKAGES - [:eeprom],

  # No IR out and WS2812 on the UNO R4. WS2812 compiles but doesn't work.
  ra4m1: STANDARD_PACKAGES - [:ir_out, :led_array],

  # ESP8266 + ESP32 use a different IR library.
  esp8266: STANDARD_PACKAGES - [:ir_out] + [:ir_out_esp],
  esp32:   STANDARD_PACKAGES - [:ir_out] + [:ir_out_esp],
  
  # RP2040 can't use WS2812 yet.
  rp2040: STANDARD_PACKAGES - [:led_array],
}
PACKAGES =

File locations are defined relative to the src/lib directory.

{
  # The core package is always included.
  core: {
    description: "Core Denko Library",
    directive: nil,
    files: [
      "Denko.h",
      "DenkoDefines.h",
      "Denko.cpp",
      "DenkoCoreIO.cpp",
      "DenkoPulseInput.cpp",
      "../../vendor/board-maps/BoardMap.h",
    ]
  },
  eeprom: {
    description: "Built-in EEPROM support",
    directive: "DENKO_EEPROM",
    files: [
      "DenkoEEPROM.cpp",
    ]
  },
  one_wire: {
    description: "OneWire bus support",
    directive: "DENKO_ONE_WIRE",
    files: [
      "DenkoOneWire.cpp",
    ]
  },
  spi_bb: {
    description: "Bit Bang SPI support",
    directive: "DENKO_SPI_BB",
    files: [
      "DenkoSPI.cpp",
      "DenkoSPIBB.cpp",
    ]
  },
  spi: {
    description: "SPI support",
    directive: "DENKO_SPI",
    files: [
      "DenkoSPI.cpp",
    ]
  },
  i2c: {
    description: "I2C device support",
    directive: "DENKO_I2C",
    files: [
      "DenkoI2C.cpp",
    ]
  },
  uart_bb: {
    description: "Bit bang serial output",
    directive: "DENKO_UART_BB",
    only: [:mega, :mega168],
    files: [
      "DenkoUARTBB.cpp",
    ]
  },
  uart: {
    description: "Hardware UART I/O",
    directive: "DENKO_UART",
    files: [
      "DenkoUART.cpp",
    ]
  },
  servo: {
    description: "Servo support",
    directive: "DENKO_SERVO",
    files: [
      "DenkoServo.cpp",
    ]
  },
  tone: {
    description: "Tone support",
    directive: "DENKO_TONE",
    files: [
      "DenkoTone.cpp",
    ]
  },
  ir_out: {
    description: "Transmit infrared signals",
    directive: "DENKO_IR_OUT",
    exclude: [:esp8266, :esp32],
    files: [
      "DenkoIROut.cpp",
    ]
  },
  ir_out_esp: {
    description: "Transmit infrared signals with the ESP8266 and ESP32",
    directive: "DENKO_IR_OUT",
    only: [:esp8266, :esp32],
    files: [
      "DenkoIROutESP.cpp",
    ]
  },
  led_array: {
    description: "Support for various protocols that control (RGB) LED arrays.",
    directive: "DENKO_LED_ARRAY",
    files: [
      "DenkoLEDArray.cpp",
    ]
  }
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helper

#error, #missing_files, #targets, #usage

Constructor Details

#initialize(options = {}) ⇒ Generator

Returns a new instance of Generator.



12
13
14
15
# File 'lib/denko_cli/generator.rb', line 12

def initialize(options={})
  @options = options
  append_target
end

Instance Attribute Details

#optionsObject

Returns the value of attribute options.



10
11
12
# File 'lib/denko_cli/generator.rb', line 10

def options
  @options
end

Class Method Details

.run!(options = {}) ⇒ Object



25
26
27
28
29
30
31
32
# File 'lib/denko_cli/generator.rb', line 25

def self.run!(options={})
  instance = self.new(options)

  instance.read
  instance.target_config
  instance.user_config
  instance.write
end

Instance Method Details

#append_targetObject



17
18
19
20
21
22
23
# File 'lib/denko_cli/generator.rb', line 17

def append_target
  options[:target] = :mega unless options[:target]
  # Preserve the source sketch name, since we need to copy that file.
  options[:src_sketch_name] = options[:sketch_name].dup
  options[:sketch_name] << "_#{options[:target]}" unless options[:target] == :mega
  options[:sketch_name] << "_#{::Denko::VERSION}"
end

#define(directive) ⇒ Object



108
109
110
# File 'lib/denko_cli/generator.rb', line 108

def define(directive)
  gsub_defines /\/\/.*#{directive}\s*$/, "#define #{directive}"
end

#gsub_defines(from, to) ⇒ Object

Run gsub! on contents of src/lib/Denko.h specifically.



113
114
115
116
117
118
119
# File 'lib/denko_cli/generator.rb', line 113

def gsub_defines(from, to)
  @packages[:core][:files].each do |f|
    if f[:path].match /DenkoDefines.h/
      f[:contents].gsub!(from, to)
    end
  end
end

#make_output_dirObject



156
157
158
159
160
# File 'lib/denko_cli/generator.rb', line 156

def make_output_dir
  dir = File.join options[:working_dir], options[:sketch_name]
  Dir::mkdir(dir) unless File.directory?(dir)
  dir
end

#output_dirObject



152
153
154
# File 'lib/denko_cli/generator.rb', line 152

def output_dir
  options[:output_dir] ||= make_output_dir
end

#readObject



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/denko_cli/generator.rb', line 34

def read
  @packages = PACKAGES
  files_missing = false

  # Replace each filepath with a hash containing the filepath and contents.
  @packages.each_key do |k|
    @packages[k][:files].map! do |f|
      contents = File.read(File.join(options[:src_dir], "lib", f))
      #
      # Not using this anymroe. User has to install their own Arduino libraries.
      #
      # If the file is in src/vendor, it gets wrapped in an #ifdef
      # corresponding to the package directive. The entire package can now
      # be toggled in DenkoDefines.h. Without this, IDE would try to compile anyway.
      # if f.match /\Avendor/
      #  directive = @packages[k][:directive]
      #  contents = "#include \"DenkoDefines.h\"\n#ifdef #{directive}\n" << contents << "\n#endif\n"
      # end
      { path: f, contents: contents }
    rescue
      files_missing = true
      puts "File missing: #{f}"
    end
  end
  
  # Show the text file when vendor files are missing
  if files_missing
    missing_files
  end

  # Read in the sketch itself.
  @sketch = File.read File.join(options[:src_dir], "#{options[:src_sketch_name]}.ino")
end

#sketch_filenameObject



168
169
170
# File 'lib/denko_cli/generator.rb', line 168

def sketch_filename
  "#{options[:sketch_name]}.ino"
end

#target_configObject



97
98
99
100
101
102
103
104
105
106
# File 'lib/denko_cli/generator.rb', line 97

def target_config
  target_packages = TARGETS[options[:target]]
  target_packages.each do |t|
    directive = PACKAGES[t][:directive]
    define(directive) if directive
  end

  # Define the DENKO VERSION
  gsub_defines("#define DENKO_VERSION __VERSION__", "#define DENKO_VERSION \"#{::Denko::VERSION}\"");
end

#user_configObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/denko_cli/generator.rb', line 68

def user_config
  if options[:baud] && serial?
    @sketch.gsub! "115200", options[:baud]
  end
  if options[:mac] && ethernet?
    octets = @options[:mac].split(':')
    bytes = octets.map { |o| "0x#{o.upcase}" }
    @sketch.gsub! "{ 0xDE, 0xAD, 0xBE, 0x30, 0x31, 0x32 }", bytes.inspect.gsub("[", "{").gsub("]", "}").gsub("\"", "")
  end
  if options[:ip] && ethernet?
    @sketch.gsub! "192,168,0,77", options[:ip].gsub(".", ",")
  end
  if options[:ssid] && wifi?
    @sketch.gsub! "yourNetwork", options[:ssid]
  end
  if options[:password] && wifi?
    @sketch.gsub! "yourPassword", options[:password]
  end
  if options[:port]
    @sketch.gsub! "int port = 3466", "int port = #{options[:port]}"
  end
  if options[:debug]
    define("debug")
  end
  unless serial?
    define("TXRX_SPI")
  end
end

#writeObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/denko_cli/generator.rb', line 121

def write
  # Write the sketch itself first.
  sketch = File.join(output_dir, sketch_filename)
  File.open(sketch, 'w') { |f| f.write @sketch }

  # Go through the @packages hash and copy source files.
  # Exclude only for target hardware incompatibility.
  # Eg. ESP8266 IR library is different and incompatible with AVR version.
  @packages.each_key do |k|
    # Check if the package should be included for this target.
    package = @packages[k]
    targeted = !package[:only] || package[:only].include?(options[:target])
    excluded = package[:exclude] && package[:exclude].include?(options[:target])

    # Append source file basename to the output dir to get output file path.
    # Then write the file contents to the destination path.
    if (targeted && !excluded)
      package[:files].each do |file|
        next unless file
        dest_path = File.join(output_dir, file[:path].split('/')[-1])
        File.open(dest_path, 'w') { |f| f.write file[:contents] }
      end
    end
  end

  # Return the location of the sketch file.
  options[:sketch_file] = sketch
  options[:sketch_folder] = output_dir
  options
end