breadkit 0.1.0 → 0.2.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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.codespellignore +2 -0
  3. data/.yamllint +16 -0
  4. data/README.md +109 -6
  5. data/data/boards/double_full.yml +18 -0
  6. data/data/boards/stripboard.yml +9 -0
  7. data/data/boards/universal.yml +8 -0
  8. data/data/parts/2n3904.yml +11 -0
  9. data/data/parts/2n7000_onsemi_to92.yml +10 -0
  10. data/data/parts/74hc00.yml +22 -0
  11. data/data/parts/74hc04.yml +22 -0
  12. data/data/parts/74hc08.yml +22 -0
  13. data/data/parts/74hc14.yml +22 -0
  14. data/data/parts/74hc165.yml +24 -0
  15. data/data/parts/74hc32.yml +22 -0
  16. data/data/parts/74hc595.yml +24 -0
  17. data/data/parts/active_buzzer.yml +8 -0
  18. data/data/parts/ams1117_3v3.yml +12 -0
  19. data/data/parts/arduino_nano.yml +70 -0
  20. data/data/parts/arduino_uno.yml +29 -26
  21. data/data/parts/atmega328p.yml +37 -0
  22. data/data/parts/attiny85.yml +16 -0
  23. data/data/parts/battery_box.yml +8 -0
  24. data/data/parts/cd4017.yml +24 -0
  25. data/data/parts/ck_bd04.yml +29 -0
  26. data/data/parts/crystal.yml +7 -0
  27. data/data/parts/dc_jack_2wire.yml +8 -0
  28. data/data/parts/diode.yml +1 -0
  29. data/data/parts/esp32_devkitc_v4_wroom32e.yml +86 -0
  30. data/data/parts/irlz44n.yml +10 -0
  31. data/data/parts/l293d.yml +24 -0
  32. data/data/parts/led.yml +1 -0
  33. data/data/parts/lm358.yml +16 -0
  34. data/data/parts/lm393.yml +16 -0
  35. data/data/parts/ne555.yml +2 -2
  36. data/data/parts/ntc_thermistor.yml +7 -0
  37. data/data/parts/photoresistor.yml +8 -0
  38. data/data/parts/pico.yml +89 -0
  39. data/data/parts/resistor.yml +1 -0
  40. data/data/parts/sc56_11ewa.yml +30 -0
  41. data/data/parts/seeed_xiao_samd21.yml +37 -0
  42. data/data/parts/shillehtek_mb102_4pin.yml +23 -0
  43. data/data/parts/slide_switch_spst.yml +8 -0
  44. data/data/parts/sparkfun_pro_micro_5v.yml +58 -0
  45. data/data/parts/transistor.yml +0 -1
  46. data/data/parts/ua7805.yml +10 -0
  47. data/data/parts/uln2003a.yml +23 -0
  48. data/data/parts/wp154a4sureqbfzgc.yml +11 -0
  49. data/docs/CLI.md +108 -0
  50. data/docs/COMPATIBILITY.md +60 -0
  51. data/docs/DECLARATIVE.md +138 -0
  52. data/docs/IR.md +42 -0
  53. data/docs/JUMPER_KIT.md +45 -0
  54. data/docs/LOCK.md +75 -0
  55. data/docs/LSP.md +59 -0
  56. data/docs/MB102_POWER.md +11 -0
  57. data/docs/MCP.md +44 -0
  58. data/docs/RAKE.md +19 -0
  59. data/docs/dsl.md +197 -6
  60. data/exe/breadkit-lsp +13 -0
  61. data/exe/breadkit-mcp +12 -0
  62. data/lib/breadkit/analysis.rb +174 -50
  63. data/lib/breadkit/board.rb +152 -4
  64. data/lib/breadkit/cli.rb +229 -13
  65. data/lib/breadkit/color.rb +38 -0
  66. data/lib/breadkit/dc_analysis.rb +261 -0
  67. data/lib/breadkit/dsl.rb +205 -24
  68. data/lib/breadkit/exporters.rb +202 -0
  69. data/lib/breadkit/formatter.rb +52 -0
  70. data/lib/breadkit/fritzing_export.rb +256 -0
  71. data/lib/breadkit/ir.rb +154 -22
  72. data/lib/breadkit/jumper_kit.rb +127 -0
  73. data/lib/breadkit/lsp_server.rb +248 -0
  74. data/lib/breadkit/mcp_server.rb +133 -0
  75. data/lib/breadkit/model.rb +47 -5
  76. data/lib/breadkit/part_library.rb +156 -3
  77. data/lib/breadkit/part_lock.rb +107 -0
  78. data/lib/breadkit/patterns.rb +119 -0
  79. data/lib/breadkit/rake_task.rb +25 -0
  80. data/lib/breadkit/resolver.rb +160 -28
  81. data/lib/breadkit/structured_input.rb +275 -0
  82. data/lib/breadkit/templates/555.bk.rb +34 -0
  83. data/lib/breadkit/templates/arduino.bk.rb +17 -0
  84. data/lib/breadkit/templates/led.bk.rb +21 -0
  85. data/lib/breadkit/value.rb +42 -3
  86. data/lib/breadkit/version.rb +1 -1
  87. data/lib/breadkit/wire_suggestions.rb +106 -0
  88. data/lib/breadkit.rb +28 -3
  89. data/package.json +1 -1
  90. data/schema/board-v1.json +38 -0
  91. data/schema/ir-v1.json +56 -11
  92. data/schema/ir-v2.json +173 -0
  93. data/schema/part-v1.json +52 -4
  94. data/sig/breadkit.rbs +107 -5
  95. metadata +119 -14
  96. data/docs/assets/01_led_button.svg +0 -12
  97. data/site/assets/01_led_button.svg +0 -14
  98. data/site/assets/03_arduino_blink.svg +0 -14
  99. data/site/favicon.svg +0 -7
  100. data/site/guide/components/index.html +0 -113
  101. data/site/guide/dsl/index.html +0 -132
  102. data/site/guide/index.html +0 -163
  103. data/site/index.html +0 -95
  104. data/site/styles.css +0 -14
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ title "NE555 astable LED blinker"
4
+ board :half
5
+ supply :USB, voltage: 5, plus: "B+1", minus: "B-1"
6
+ net :VCC, at: "B+1"
7
+ net :GND, at: "B-1"
8
+
9
+ ic :U1, "NE555", at: "e20"
10
+ resistor :R1, "1k", pins: %w[g30 g21]
11
+ resistor :R2, "10k", pins: %w[h21 h22]
12
+ resistor :R3, "330", pins: %w[a22 a25]
13
+ capacitor :C1, "10n", pins: %w[h23 h24]
14
+ electrolytic :C2, "100u", plus: "g22", minus: "j24"
15
+ led :D1, color: :red, anode: "b25", cathode: "b27"
16
+
17
+ wire "i30", "B+", color: :red
18
+ wire "g20", "B+", color: :red
19
+ wire "b20", "B-", color: :black
20
+ wire "b21", "i22", color: :yellow
21
+ wire "a23", "B+", color: :red
22
+ wire "a27", "B-", color: :black
23
+ wire "i24", "B-", color: :black
24
+
25
+ expect do
26
+ connected "U1.1", :GND
27
+ connected "U1.2", "U1.6", "C2.plus"
28
+ connected "U1.4", :VCC
29
+ connected "U1.8", :VCC
30
+ connected "U1.3", "R3.1"
31
+ connected "R3.2", "D1.anode"
32
+ connected "D1.cathode", :GND
33
+ isolated :VCC, :GND
34
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ title "Arduino UNO LED"
4
+ board :half
5
+ offboard :UNO, "arduino_uno", side: :left
6
+
7
+ resistor :R1, "330", pins: %w[a10 a14]
8
+ led :D1, color: :red, anode: "b14", cathode: "b16"
9
+
10
+ wire "UNO.D13", "c10", color: :yellow
11
+ wire "a16", "UNO.GND", color: :black
12
+
13
+ expect do
14
+ connected "UNO.D13", "R1.1"
15
+ connected "R1.2", "D1.anode"
16
+ connected "D1.cathode", "UNO.GND"
17
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ title "LED with current-limiting resistor"
4
+ board :half
5
+
6
+ supply :USB, voltage: 5, plus: "B+1", minus: "B-1"
7
+ net :VCC, at: "B+1"
8
+ net :GND, at: "B-1"
9
+
10
+ resistor :R1, "330", pins: %w[a10 a14]
11
+ led :D1, color: :red, anode: "b14", cathode: "b16"
12
+
13
+ wire "c10", "B+", color: :red
14
+ wire "a16", "B-", color: :black
15
+
16
+ expect do
17
+ connected :VCC, "R1.1"
18
+ connected "R1.2", "D1.anode"
19
+ connected "D1.cathode", :GND
20
+ isolated :VCC, :GND
21
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Breadkit
4
4
  class Value
5
+ QUALIFIER = /\s+(\d+(?:\.\d+)?%|\d+(?:\.\d+)?(?:\/\d+(?:\.\d+)?)?[WV])\z/i
5
6
  MULTIPLIERS = { "p" => 1e-12, "n" => 1e-9, "u" => 1e-6, "m" => 1e-3,
6
7
  "R" => 1.0, "r" => 1.0, "k" => 1e3, "K" => 1e3,
7
8
  "M" => 1e6, "G" => 1e9 }.freeze
@@ -13,7 +14,7 @@ module Breadkit
13
14
  def self.parse(input)
14
15
  return input.to_f if input.is_a?(Numeric)
15
16
 
16
- text = input.to_s.strip.sub(/(?:Ω|Ω|ohm|[FfHhVv])\z/i, "").tr("µμ", "uu")
17
+ text = split_spec(input).first.sub(/(?:Ω|Ω|ohm|[FfHhVv])\z/i, "").tr("µμ", "uu")
17
18
  if (match = /\A(\d*)([pnuRrmkKMG])(\d+)\z/.match(text))
18
19
  whole = match[1].empty? ? 0 : match[1].to_i
19
20
  return (whole + match[3].to_f / (10**match[3].length)) * MULTIPLIERS.fetch(match[2])
@@ -24,8 +25,46 @@ module Breadkit
24
25
  match[1].to_f * MULTIPLIERS.fetch(match[2], 1.0)
25
26
  end
26
27
 
27
- def initialize(value)
28
- @unit = value.to_s.strip[/\A.*?(Ω|Ω|ohm|[FfHhVv])\z/i, 1]&.then { |suffix| %w[Ω Ω ohm].include?(suffix.downcase) ? "Ω" : suffix.upcase } || "Ω"
28
+ def self.tolerance(input)
29
+ suffix = split_spec(input).last.find { |item| item.end_with?("%") }
30
+ suffix && suffix.to_f / 100.0
31
+ end
32
+
33
+ def self.power_rating(input)
34
+ suffix = split_spec(input).last.find { |item| item.upcase.end_with?("W") }
35
+ return unless suffix
36
+
37
+ quantity = suffix[0...-1]
38
+ quantity.include?("/") ? quantity.split("/").map(&:to_f).reduce(:/) : quantity.to_f
39
+ end
40
+
41
+ def self.voltage_rating(input)
42
+ suffix = split_spec(input).last.find { |item| item.upcase.end_with?("V") }
43
+ suffix&.to_f
44
+ end
45
+
46
+ def self.split_spec(input)
47
+ base = input.to_s.strip
48
+ qualifiers = []
49
+ while (match = QUALIFIER.match(base))
50
+ qualifiers.unshift(match[1])
51
+ base = base[0...match.begin(0)]
52
+ end
53
+ kinds = qualifiers.map { |item| item[-1].upcase }
54
+ raise ArgumentError, "duplicate value qualifier: #{input.inspect}" unless kinds.uniq == kinds
55
+ qualifiers.each do |item|
56
+ quantity = item[0...-1]
57
+ numbers = quantity.split("/").map(&:to_f)
58
+ invalid = numbers.any? { |number| !number.finite? || !number.positive? } || (item.end_with?("%") && numbers.first >= 100)
59
+ raise ArgumentError, "invalid value qualifier: #{item}" if invalid
60
+ end
61
+ [base, qualifiers]
62
+ end
63
+
64
+ def initialize(value, category: nil)
65
+ explicit = self.class.split_spec(value).first[/\A.*?(Ω|Ω|ohm|[FfHhVv])\z/i, 1]
66
+ @unit = explicit&.then { |suffix| %w[Ω Ω ohm].include?(suffix.downcase) ? "Ω" : suffix.upcase } ||
67
+ { resistor: "Ω", capacitor: "F", electrolytic: "F", inductor: "H", supply: "V" }.fetch(category&.to_sym, "")
29
68
  @value = self.class.parse(value)
30
69
  freeze
31
70
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Breadkit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Breadkit
4
+ class WireSuggestions
5
+ def initialize(circuit)
6
+ @circuit = circuit
7
+ end
8
+
9
+ def call
10
+ return [] if circuit.diagnostics.any? { |item| item.severity == "error" }
11
+ return [] unless circuit.potentials.conflicts.empty?
12
+
13
+ occupied = occupied_holes
14
+ bodies = component_bodies
15
+ proposed_nets = UnionFind.new
16
+ proposed_potentials = {}
17
+ circuit.expectations.flat_map do |group|
18
+ next [] if group[:when] || group["when"]
19
+
20
+ Array(group[:entries] || group["entries"]).filter_map do |entry|
21
+ next unless (entry[:kind] || entry["kind"]) == "connected"
22
+
23
+ refs = Array(entry[:refs] || entry["refs"]).map(&:to_s)
24
+ next unless refs.length == 2
25
+
26
+ first, second = refs.map { |ref| placed_pin_hole(ref) }
27
+ next unless first && second
28
+ left_net, right_net = refs.map { |ref| circuit.net_of(ref) }
29
+ next unless left_net && right_net && left_net != right_net
30
+
31
+ left_root = proposed_nets.find(left_net.name)
32
+ right_root = proposed_nets.find(right_net.name)
33
+ next if left_root == right_root
34
+
35
+ left_potential = proposed_potentials.fetch(left_root, left_net.potential)
36
+ right_potential = proposed_potentials.fetch(right_root, right_net.potential)
37
+ next if left_potential && right_potential && (left_potential - right_potential).abs > 1e-9
38
+
39
+ left = free_strip_holes(first, occupied, bodies)
40
+ right = free_strip_holes(second, occupied, bodies)
41
+ next if left.empty? || right.empty?
42
+
43
+ from, to = left.product(right).min_by do |a, b|
44
+ [(a.x - b.x)**2 + (a.y - b.y)**2, a.x, a.y, b.x, b.y, a.id, b.id]
45
+ end
46
+ occupied[from.id] = true
47
+ occupied[to.id] = true
48
+ proposed_nets.union(left_root, right_root)
49
+ proposed_potentials[proposed_nets.find(left_root)] = left_potential || right_potential
50
+ { from: from.id, to: to.id, refs: refs }
51
+ end
52
+ end.uniq
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :circuit
58
+
59
+ def placed_pin_hole(reference)
60
+ parsed = HoleId.parse(reference, board: circuit.board)
61
+ return unless parsed.kind == :pin
62
+
63
+ hole_id = circuit.components[parsed.ref]&.pin(parsed.pin)&.hole_id
64
+ hole_id if hole_id && circuit.board.hole(hole_id)
65
+ rescue ArgumentError
66
+ nil
67
+ end
68
+
69
+ def free_strip_holes(pin_hole, occupied, bodies)
70
+ Array(circuit.board.strip(pin_hole)).filter_map { |id| circuit.board.hole(id) }
71
+ .reject { |hole| occupied[hole.id] || bodies.any? { |body| covered?(hole, body) } }
72
+ end
73
+
74
+ def occupied_holes
75
+ ids = circuit.components.values.flat_map { |component| component.pins.values.filter_map(&:hole_id) }
76
+ ids.concat(circuit.supplies.flat_map { |supply| [supply.plus, supply.minus] })
77
+ circuit.wires.each do |wire|
78
+ next if wire.electrical == false
79
+
80
+ [wire.from, wire.to].each do |endpoint|
81
+ hole = circuit.board.hole(endpoint)
82
+ ids << hole.id if hole
83
+ end
84
+ end
85
+ ids.to_h { |id| [id, true] }
86
+ end
87
+
88
+ def component_bodies
89
+ circuit.components.values.filter_map do |component|
90
+ component.body_bounds(circuit.board)
91
+ end
92
+ end
93
+
94
+ def covered?(hole, bounds)
95
+ left, top, width, height = bounds
96
+ hole.x > left + 1e-6 && hole.x < left + width - 1e-6 &&
97
+ hole.y > top + 1e-6 && hole.y < top + height - 1e-6
98
+ end
99
+ end
100
+
101
+ class Circuit
102
+ def wire_suggestions
103
+ WireSuggestions.new(self).call
104
+ end
105
+ end
106
+ end
data/lib/breadkit.rb CHANGED
@@ -6,21 +6,46 @@ require "did_you_mean"
6
6
 
7
7
  require_relative "breadkit/version"
8
8
  require_relative "breadkit/value"
9
+ require_relative "breadkit/color"
9
10
  require_relative "breadkit/hole_id"
10
11
  require_relative "breadkit/model"
11
12
  require_relative "breadkit/board"
12
13
  require_relative "breadkit/part_library"
13
14
  require_relative "breadkit/dsl"
15
+ require_relative "breadkit/structured_input"
16
+ require_relative "breadkit/part_lock"
17
+ require_relative "breadkit/formatter"
14
18
  require_relative "breadkit/resolver"
15
19
  require_relative "breadkit/analysis"
20
+ require_relative "breadkit/wire_suggestions"
21
+ require_relative "breadkit/jumper_kit"
22
+ require_relative "breadkit/dc_analysis"
23
+ require_relative "breadkit/patterns"
16
24
  require_relative "breadkit/ir"
25
+ require_relative "breadkit/exporters"
26
+ require_relative "breadkit/fritzing_export"
17
27
  require_relative "breadkit/cli"
18
28
 
19
29
  module Breadkit
20
30
  class Error < StandardError; end
21
- class DSLError < Error; end
31
+ class DSLError < Error
32
+ attr_reader :location
22
33
 
23
- def self.load(path)
24
- path.end_with?(".json") ? IR::Reader.new.read_file(path) : Resolver.new.call(DSL.load_file(path))
34
+ def initialize(message, location: nil)
35
+ super(message)
36
+ @location = location
37
+ end
38
+ end
39
+
40
+ def self.load(path, timeout: 10)
41
+ return IR::Reader.new.read_file(path) if path.end_with?(".json")
42
+
43
+ document = if path.end_with?(".bk.yml", ".bk.yaml", ".bk.toml")
44
+ StructuredInput.load_file(path)
45
+ else
46
+ DSL.load_file(path, timeout: timeout)
47
+ end
48
+ PartLock.verify(document, path)
49
+ Resolver.new.call(document)
25
50
  end
26
51
  end
data/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": true,
4
4
  "version": "0.0.0",
5
5
  "scripts": {
6
- "build": "mkdir -p _site && cp -R site/. _site/ && tailwindcss -i site/styles.css -o _site/assets/site.css --minify"
6
+ "build": "mkdir -p _site/schema && cp -R site/. _site/ && cp -R schema/. _site/schema/ && tailwindcss -i site/styles.css -o _site/assets/site.css --minify"
7
7
  },
8
8
  "devDependencies": {
9
9
  "@tailwindcss/cli": "4.3.3",
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://breadkit.github.io/breadkit/schema/board-v1.json",
4
+ "title": "Breadkit board definition v1",
5
+ "type": "object",
6
+ "required": ["id", "terminal"],
7
+ "properties": {
8
+ "id": { "type": "string", "minLength": 1 },
9
+ "name": { "type": "string" },
10
+ "ground_labels": { "type": "array", "items": { "type": "string" } },
11
+ "terminal": {
12
+ "type": "object", "required": ["columns", "rows", "groups"],
13
+ "properties": {
14
+ "columns": { "type": "integer", "minimum": 1 },
15
+ "rows": { "type": "array", "minItems": 1, "items": { "type": "string", "minLength": 1 } },
16
+ "groups": { "type": "array", "items": { "type": "array", "minItems": 1, "items": { "type": "string" } } },
17
+ "ravine_between": { "type": "array", "prefixItems": [{ "type": "string" }, { "type": "string" }], "minItems": 2, "maxItems": 2 },
18
+ "strip_direction": { "enum": ["column", "row"] },
19
+ "wire_attachment": { "enum": ["socket", "solder"] }
20
+ }, "additionalProperties": false
21
+ },
22
+ "rails": {
23
+ "type": "array", "items": { "type": "object", "required": ["id", "side", "order"],
24
+ "properties": { "id": { "type": "string" }, "side": { "enum": ["top", "bottom", "left", "right", "center"] }, "order": { "type": "integer", "minimum": 0 }, "polarity": { "enum": ["+", "-"] } },
25
+ "additionalProperties": false }
26
+ },
27
+ "rail_layout": {
28
+ "type": "object", "required": ["segments"],
29
+ "properties": {
30
+ "holes": { "type": "integer", "minimum": 1 }, "group_size": { "type": "integer", "minimum": 1 },
31
+ "start_column": { "type": "integer", "minimum": 1 }, "start_row": { "type": "integer", "minimum": 1 },
32
+ "segments": { "$ref": "#/$defs/segments" }, "split_segments": { "$ref": "#/$defs/segments" }
33
+ }, "additionalProperties": false
34
+ }
35
+ },
36
+ "additionalProperties": false,
37
+ "$defs": { "segments": { "type": "array", "items": { "type": "array", "prefixItems": [{ "type": "integer" }, { "type": "integer" }], "minItems": 2, "maxItems": 2 } } }
38
+ }
data/schema/ir-v1.json CHANGED
@@ -1,21 +1,16 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://breadkit.github.io/breadkit/schema/ir-v1.json",
3
4
  "title": "Breadkit IR v1",
4
5
  "type": "object",
5
6
  "required": ["schema_version", "title", "board", "components", "wires", "supplies", "labels", "expectations"],
6
7
  "properties": {
7
8
  "schema_version": { "const": 1 },
9
+ "source_root": { "type": "string" },
8
10
  "title": { "type": ["string", "null"] },
9
- "part_definitions": {
10
- "type": "array",
11
- "items": {
12
- "type": "object",
13
- "required": ["id", "pins"],
14
- "properties": { "id": { "type": "string" }, "pins": { "type": "array", "items": { "type": "object" } } },
15
- "additionalProperties": true
16
- }
17
- },
18
- "board_definition": { "type": "object" },
11
+ "steps": { "type": "array", "items": { "$ref": "#/$defs/step" } },
12
+ "part_definitions": { "type": "array", "items": { "$ref": "#/$defs/partDefinition" } },
13
+ "board_definition": { "$ref": "#/$defs/boardDefinition" },
19
14
  "lint_disables": { "type": "array", "items": { "type": "object" } },
20
15
  "board": {
21
16
  "type": "object",
@@ -38,12 +33,53 @@
38
33
  },
39
34
  "additionalProperties": true,
40
35
  "$defs": {
36
+ "partDefinition": {
37
+ "type": "object", "required": ["id", "pins"],
38
+ "properties": {
39
+ "id": { "type": "string", "minLength": 1 },
40
+ "datasheet_url": { "type": "string", "format": "uri", "pattern": "^https://" },
41
+ "pins": { "type": "array", "items": { "type": "object", "required": ["num"],
42
+ "properties": { "num": { "type": ["string", "integer"] }, "name": { "type": "string" }, "type": { "type": "string" }, "role": { "type": "string" }, "max_current": { "type": "number", "exclusiveMinimum": 0 } } } },
43
+ "placement": { "enum": ["leads", "dip", "footprint", "offboard"] },
44
+ "max_lead_span_mm": { "type": "number", "exclusiveMinimum": 0 },
45
+ "polarity": { "type": "object" }, "footprint": { "type": "object" },
46
+ "provides": { "type": "array", "items": { "type": "object", "required": ["positive", "negative", "voltage"],
47
+ "properties": { "positive": { "type": "string" }, "negative": { "type": "string" }, "voltage": { "type": "number", "exclusiveMinimum": 0 } } } }
48
+ }
49
+ },
50
+ "boardDefinition": {
51
+ "type": "object", "required": ["id", "terminal"],
52
+ "properties": {
53
+ "id": { "type": "string", "minLength": 1 },
54
+ "terminal": { "type": "object", "required": ["columns", "rows", "groups"],
55
+ "properties": {
56
+ "columns": { "type": "integer", "minimum": 1 },
57
+ "rows": { "type": "array", "items": { "type": "string" } },
58
+ "groups": { "type": "array", "items": { "type": "array", "items": { "type": "string" } } },
59
+ "ravine_between": { "type": "array", "items": { "type": "string" }, "minItems": 2, "maxItems": 2 },
60
+ "strip_direction": { "enum": ["column", "row"] },
61
+ "wire_attachment": { "enum": ["socket", "solder"] }
62
+ } },
63
+ "rails": { "type": "array", "items": { "type": "object", "required": ["id"],
64
+ "properties": { "id": { "type": "string" }, "side": { "type": "string" }, "order": { "type": "integer" }, "polarity": { "enum": ["+", "-"] } } } },
65
+ "rail_layout": { "type": "object", "properties": { "segments": { "type": "array" }, "split_segments": { "type": "array" } } }
66
+ }
67
+ },
41
68
  "source": {
42
69
  "type": "object",
43
70
  "required": ["path", "line"],
44
71
  "properties": { "path": { "type": "string" }, "line": { "type": "integer", "minimum": 1 } }
45
72
  },
46
73
  "nullableSource": { "anyOf": [{ "type": "null" }, { "$ref": "#/$defs/source" }] },
74
+ "step": {
75
+ "type": "object",
76
+ "required": ["number"],
77
+ "properties": {
78
+ "number": { "type": "integer", "minimum": 1 },
79
+ "title": { "type": ["string", "null"], "minLength": 1 },
80
+ "source": { "$ref": "#/$defs/nullableSource" }
81
+ }
82
+ },
47
83
  "location": {
48
84
  "type": "object",
49
85
  "required": ["path", "line"],
@@ -59,6 +95,7 @@
59
95
  "attrs": { "type": "object" },
60
96
  "pins": { "type": "object", "additionalProperties": { "type": ["string", "null"] } },
61
97
  "unused": { "type": "array", "items": { "type": "string" } },
98
+ "step": { "type": "integer", "minimum": 1 },
62
99
  "source": { "$ref": "#/$defs/nullableSource" }
63
100
  }
64
101
  },
@@ -71,6 +108,7 @@
71
108
  "to": { "type": "string" },
72
109
  "color": { "type": ["string", "null"] },
73
110
  "route": { "type": "string" },
111
+ "step": { "type": "integer", "minimum": 1 },
74
112
  "source": { "$ref": "#/$defs/nullableSource" }
75
113
  }
76
114
  },
@@ -80,8 +118,12 @@
80
118
  "properties": {
81
119
  "name": { "type": "string" },
82
120
  "voltage": { "type": "number" },
121
+ "isolated": { "type": "boolean" },
122
+ "voltage_range": { "type": ["array", "null"], "prefixItems": [{ "type": "number", "exclusiveMinimum": 0 }, { "type": "number", "exclusiveMinimum": 0 }], "minItems": 2, "maxItems": 2 },
123
+ "current_limit": { "type": ["number", "null"], "exclusiveMinimum": 0 },
83
124
  "plus": { "type": "string" },
84
125
  "minus": { "type": "string" },
126
+ "step": { "type": "integer", "minimum": 1 },
85
127
  "source": { "$ref": "#/$defs/nullableSource" }
86
128
  }
87
129
  },
@@ -91,6 +133,7 @@
91
133
  "properties": {
92
134
  "net": { "type": "string" },
93
135
  "at": { "type": "string" },
136
+ "step": { "type": "integer", "minimum": 1 },
94
137
  "source": { "$ref": "#/$defs/nullableSource" }
95
138
  }
96
139
  },
@@ -99,14 +142,16 @@
99
142
  "required": ["strict", "entries", "location"],
100
143
  "properties": {
101
144
  "strict": { "type": "boolean" },
145
+ "when": { "type": "string", "minLength": 1 },
102
146
  "entries": {
103
147
  "type": "array",
104
148
  "items": {
105
149
  "type": "object",
106
150
  "required": ["kind", "refs", "location"],
107
151
  "properties": {
108
- "kind": { "enum": ["connected", "isolated", "net"] },
152
+ "kind": { "enum": ["connected", "isolated", "net", "voltage", "current"] },
109
153
  "refs": { "type": "array", "items": { "type": "string" } },
154
+ "range": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
110
155
  "location": { "$ref": "#/$defs/location" }
111
156
  }
112
157
  }