///|
fn finding(
code : String,
message : String,
devices : Array[String],
resource : String,
) -> Finding {
{ code, message, devices, resource, }
}
///|
/// Validate device declarations independently of a chosen resource assignment.
pub fn validate_devices(devices : Array[Device]) -> Array[Finding] {
let findings : Array[Finding] = []
if devices.length() > 16 {
findings.push(
finding(
"DEVICE_LIMIT",
"At most 16 devices are supported per request.",
[],
"devices",
),
)
}
let ids : Map[String, Bool] = Map([])
for d in devices {
if d.id.length() == 0 || d.id.length() > 80 {
findings.push(
finding(
"INVALID_ID",
"Device id must contain 1..80 UTF-16 code units.",
[d.id],
"id",
),
)
}
if ids.contains(d.id) {
findings.push(
finding(
"DUPLICATE_ID",
"Device id is used more than once.",
[d.id],
"id",
),
)
}
ids[d.id] = true
for signal, pin in d.pins {
if !signals(d.kind).contains(signal) {
findings.push(
finding(
"UNKNOWN_SIGNAL",
"Unknown signal \{signal} for \{d.kind.name()}.",
[d.id],
signal,
),
)
}
if !is_available(pin) {
findings.push(
finding(
"PIN_UNAVAILABLE",
"GPIO\{pin} is not exposed on the original Pico.",
[d.id],
"GPIO\{pin}",
),
)
}
}
if d.frequency_hz < 0 ||
d.frequency_hz > 1000000000 ||
(d.kind == Pwm && d.frequency_hz == 0) {
findings.push(
finding(
"INVALID_FREQUENCY",
"Frequency must be 0..1000000000 Hz, and positive for PWM.",
[d.id],
"frequency_hz",
),
)
}
match d.i2c_address {
Some(address) =>
if d.kind != I2c || address < 8 || address > 119 {
findings.push(
finding(
"INVALID_I2C_ADDRESS",
"Only I2C accepts normal 7-bit addresses in 8..119.",
[d.id],
"i2c_address",
),
)
}
None => ()
}
match d.instance {
Some(n) =>
if n < 0 || n > (if d.kind == Pwm { 7 } else { 1 }) {
findings.push(
finding(
"INVALID_INSTANCE",
"Instance is outside the peripheral's supported range.",
[d.id],
"instance",
),
)
}
None => ()
}
match d.bus {
Some(bus) => {
if bus.length() == 0 || bus.length() > 80 {
findings.push(
finding(
"INVALID_BUS",
"Bus name must contain 1..80 UTF-16 code units.",
[d.id],
"bus",
),
)
}
if d.kind != I2c && d.kind != Spi {
findings.push(
finding(
"BUS_KIND",
"Only I2C and SPI support explicit shared buses.",
[d.id],
bus,
),
)
}
}
None => ()
}
}
findings
}
///|
fn same_bus(a : Assignment, b : Assignment) -> Bool {
match (a.bus, b.bus) {
(Some(x), Some(y)) => x == y
_ => false
}
}
///|
fn shared_signal(
a : Assignment,
b : Assignment,
sa : String,
sb : String,
) -> Bool {
same_bus(a, b) &&
a.kind == b.kind &&
(a.kind == I2c || a.kind == Spi) &&
a.instance == b.instance &&
a.frequency_hz == b.frequency_hz &&
sa == sb &&
sa != "CS"
}
///|
fn pair_conflicts(a : Assignment, b : Assignment) -> Array[Finding] {
let findings : Array[Finding] = []
let ids = [a.id, b.id]
let shared = same_bus(a, b)
if shared && a.kind == I2c && b.kind == I2c {
match (a.i2c_address, b.i2c_address) {
(Some(x), Some(y)) =>
if x == y {
findings.push(
finding(
"I2C_ADDRESS_CONFLICT", "Devices on a shared I2C bus declare the same address; change hardware addressing or separate buses.",
ids, "i2c_address",
),
)
}
_ => ()
}
}
if shared {
if a.kind != b.kind || (a.kind != I2c && a.kind != Spi) {
findings.push(
finding(
"BUS_KIND", "A shared bus name cannot span different peripheral kinds.",
ids, "bus",
),
)
} else {
if a.instance != b.instance {
findings.push(
finding(
"BUS_INSTANCE", "A shared bus must use one peripheral instance.", ids,
"bus",
),
)
}
if a.frequency_hz != b.frequency_hz {
findings.push(
finding(
"BUS_FREQUENCY", "Shared bus frequency metadata must match exactly.",
ids, "bus",
),
)
}
for signal in signals(a.kind) {
if signal != "CS" && a.pins.get(signal) != b.pins.get(signal) {
findings.push(
finding(
"BUS_ROUTING",
"Shared \{signal} must use the same GPIO.",
ids,
signal,
),
)
}
}
}
}
if a.kind == b.kind && a.kind != Pwm && a.instance == b.instance && !shared {
findings.push(
finding(
"INSTANCE_CONFLICT",
"Independent devices cannot reserve the same peripheral instance.",
ids,
"\{a.kind.name()}\{a.instance}",
),
)
}
for sa in signals(a.kind) {
match a.pins.get(sa) {
Some(pa) =>
for sb in signals(b.kind) {
match b.pins.get(sb) {
Some(pb) =>
if pa == pb && !shared_signal(a, b, sa, sb) {
findings.push(
finding(
"PIN_CONFLICT",
"GPIO\{pa} is reserved by both devices.",
ids,
"GPIO\{pa}",
),
)
}
None => ()
}
}
None => ()
}
}
if a.kind == Pwm && b.kind == Pwm && a.instance == b.instance {
match (a.pins.get("OUT"), b.pins.get("OUT")) {
(Some(pa), Some(pb)) =>
if pwm_channel(pa) == pwm_channel(pb) {
findings.push(
finding(
"PWM_CHANNEL",
"Two devices cannot independently use the same PWM slice/channel, even on different GPIOs.",
ids,
"PWM\{a.instance}/\{pwm_channel(pa)}",
),
)
}
_ => ()
}
if a.frequency_hz != b.frequency_hz {
findings.push(
finding(
"PWM_FREQUENCY",
"The A/B channels of a PWM slice share the configured frequency.",
ids,
"PWM\{a.instance}",
),
)
}
}
findings
}
///|
/// Validate full concrete assignments, including GPIO and peripheral conflicts.
pub fn check_assignments(
assignments : Array[Assignment],
reserved_pins? : Array[Int] = [],
) -> Array[Finding] {
let declarations = assignments.map(fn(a) {
device(
a.id,
a.kind,
pins=a.pins,
instance=Some(a.instance),
bus=a.bus,
frequency_hz=a.frequency_hz,
i2c_address=a.i2c_address,
)
})
let findings = validate_devices(declarations)
for item in validate_reserved_pins(reserved_pins) {
findings.push(item)
}
for a in assignments {
for _, pin in a.pins {
if reserved_pins.contains(pin) {
findings.push(
finding(
"PIN_RESERVED",
"GPIO is reserved for circuitry outside this plan.",
[a.id],
"GPIO\{pin}",
),
)
}
}
}
for a in assignments {
for signal in signals(a.kind) {
match a.pins.get(signal) {
None =>
findings.push(
finding(
"MISSING_SIGNAL",
"Required signal \{signal} is missing.",
[a.id],
signal,
),
)
Some(pin) =>
if !supports(a.kind, a.instance, signal, pin) {
findings.push(
finding(
"SIGNAL_MAPPING",
"GPIO\{pin} cannot carry \{a.kind.name()}\{a.instance} \{signal}.",
[a.id],
"GPIO\{pin}",
),
)
}
}
}
let assigned_signals = signals(a.kind)
for i = 0; i < assigned_signals.length(); i = i + 1 {
for j = i + 1; j < assigned_signals.length(); j = j + 1 {
match
(a.pins.get(assigned_signals[i]), a.pins.get(assigned_signals[j])) {
(Some(x), Some(y)) =>
if x == y {
findings.push(
finding(
"PIN_SELF_CONFLICT",
"Different signals on one device cannot share a GPIO.",
[a.id],
"GPIO\{x}",
),
)
}
_ => ()
}
}
}
}
for i = 0; i < assignments.length(); i = i + 1 {
for j = i + 1; j < assignments.length(); j = j + 1 {
for item in pair_conflicts(assignments[i], assignments[j]) {
findings.push(item)
}
}
}
findings
}
///|
fn validate_reserved_pins(pins : Array[Int]) -> Array[Finding] {
let findings : Array[Finding] = []
let seen : Map[Int, Bool] = Map([])
for pin in pins {
if !is_available(pin) || seen.contains(pin) {
findings.push(
finding(
"INVALID_RESERVED_PIN",
"Reserved pins must be unique exposed Pico GPIOs.",
[],
"reserved_pins",
),
)
}
seen[pin] = true
}
findings
}