Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/interface/ffi/src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,28 @@ export fn eclexiaiser_read_power_mw(raw_handle: ?*anyopaque) u64 {
return estimatePowerMw();
}

/// Measure energy consumption for a handle, in microjoules.
/// Matches the ABI declaration in Types.idr (namespace Foreign):
/// %foreign "C:eclexiaiser_measure_energy" : Bits64 -> PrimIO Bits64
/// Returns the current counter reading (uJ), or 0 if no counter is available
/// or the handle is null.
export fn eclexiaiser_measure_energy(raw_handle: ?*anyopaque) u64 {
const handle = getHandle(raw_handle) orelse return 0;

if (!handle.initialized) {
setError("Handle not initialized");
return 0;
}

const reading = readEnergyCounter(handle.counter_type) orelse {
setError("Energy counter not available");
return 0;
};

clearError();
return reading;
}

//==============================================================================
// Carbon Intensity API
//==============================================================================
Expand Down Expand Up @@ -280,6 +302,14 @@ export fn eclexiaiser_query_renewable_pct(raw_handle: ?*anyopaque, zone_id: u32)
return getStaticRenewablePct(zone_id);
}

/// Query carbon intensity for a grid zone without a library handle.
/// Matches the ABI declaration in Types.idr (namespace Foreign):
/// %foreign "C:eclexiaiser_query_carbon" : Bits32 -> PrimIO Bits32
/// Returns milligrams CO2 per kWh from the static dataset.
export fn eclexiaiser_query_carbon(zone_id: u32) u32 {
return getStaticCarbonIntensity(zone_id);
}

/// Set the carbon API provider.
export fn eclexiaiser_set_carbon_api(raw_handle: ?*anyopaque, api_source: u32) Result {
const handle = getHandle(raw_handle) orelse return .null_pointer;
Expand Down Expand Up @@ -317,6 +347,19 @@ export fn eclexiaiser_enforce_energy_budget(raw_handle: ?*anyopaque, budget_uj:
return .ok;
}

/// Enforce an energy budget without a library handle.
/// Matches the ABI declaration in Types.idr (namespace Foreign):
/// %foreign "C:eclexiaiser_enforce_budget" : Bits64 -> Bits64 -> PrimIO Bits32
/// Returns .ok (0) if within budget, .budget_exceeded (5) otherwise.
export fn eclexiaiser_enforce_budget(budget_uj: u64, measured_uj: u64) Result {
if (measured_uj > budget_uj) {
setError("Energy budget exceeded");
return .budget_exceeded;
}
clearError();
return .ok;
}

/// Enforce a carbon limit against a measurement.
export fn eclexiaiser_enforce_carbon_limit(raw_handle: ?*anyopaque, limit_mg_co2: u64, measured_mg_co2: u64) Result {
const handle = getHandle(raw_handle) orelse return .null_pointer;
Expand Down Expand Up @@ -639,3 +682,23 @@ test "struct layout sizes" {
try std.testing.expectEqual(@as(usize, 40), @sizeOf(BudgetEnforcement));
try std.testing.expectEqual(@as(usize, 40), @sizeOf(SustainabilityReport));
}

test "Types.idr Foreign: measure_energy and null handle" {
// Null handle must yield 0 (not a crash).
try std.testing.expectEqual(@as(u64, 0), eclexiaiser_measure_energy(null));

const raw_handle = eclexiaiser_init() orelse return error.InitFailed;
defer eclexiaiser_free(raw_handle);
// Estimate counter is always available, so a reading is produced.
try std.testing.expect(eclexiaiser_measure_energy(raw_handle) > 0);
}

test "Types.idr Foreign: query_carbon (handle-free)" {
try std.testing.expectEqual(@as(u32, 200_000), eclexiaiser_query_carbon(0x4742)); // GB
try std.testing.expectEqual(@as(u32, 20_000), eclexiaiser_query_carbon(0x4E4F)); // NO
}

test "Types.idr Foreign: enforce_budget (handle-free) result codes" {
try std.testing.expectEqual(Result.ok, eclexiaiser_enforce_budget(1000, 500));
try std.testing.expectEqual(Result.budget_exceeded, eclexiaiser_enforce_budget(500, 1000));
}
Loading