Skip to content

Commit 1bc7331

Browse files
committed
chore: restructure 6a2ml files into .machine_readable/6a2/
1 parent 3d7b367 commit 1bc7331

12 files changed

Lines changed: 213 additions & 26 deletions

File tree

api/v/src/ecm_api.v

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,17 @@ pub fn recommend_response(snap SpectrumSnapshot) DefensiveRecommendation {
142142

143143
// freq_in_band checks whether a frequency falls within a given band.
144144
pub fn freq_in_band(freq_hz u64, band FreqBand) bool {
145-
lower, upper := match band {
146-
.hf { u64(0), u64(30_000_000) }
147-
.vhf { u64(30_000_000), u64(300_000_000) }
148-
.uhf { u64(300_000_000), u64(3_000_000_000) }
149-
.shf { u64(3_000_000_000), u64(30_000_000_000) }
145+
lower := match band {
146+
.hf { u64(0) }
147+
.vhf { u64(30_000_000) }
148+
.uhf { u64(300_000_000) }
149+
.shf { u64(3_000_000_000) }
150+
}
151+
upper := match band {
152+
.hf { u64(30_000_000) }
153+
.vhf { u64(300_000_000) }
154+
.uhf { u64(3_000_000_000) }
155+
.shf { u64(30_000_000_000) }
150156
}
151157
return freq_hz >= lower && freq_hz < upper
152158
}

api/v/src/ecm_api_test.v

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
// ecm_api_test.v — Tests for the V-lang ECM API.
5+
6+
module ecm_api
7+
8+
// ── Point-to-point tests ────────────────────────────────────────────────
9+
10+
fn test_classify_cw_jammer() {
11+
sig := DetectedSignal{
12+
frequency_hz: 2_400_000_000
13+
bandwidth_hz: 50_000_000
14+
snr_db: 30.0
15+
modulation: .cw
16+
classification: .neutral
17+
bearing_deg: none
18+
timestamp_s: 0.0
19+
}
20+
assert classify_signal(sig) == .suspected_jammer
21+
}
22+
23+
fn test_classify_normal_ofdm() {
24+
sig := DetectedSignal{
25+
frequency_hz: 2_412_000_000
26+
bandwidth_hz: 20_000_000
27+
snr_db: 25.0
28+
modulation: .ofdm
29+
classification: .neutral
30+
bearing_deg: none
31+
timestamp_s: 0.0
32+
}
33+
assert classify_signal(sig) == .neutral
34+
}
35+
36+
fn test_classify_wideband_jammer() {
37+
sig := DetectedSignal{
38+
frequency_hz: 1_000_000_000
39+
bandwidth_hz: 50_000_000
40+
snr_db: 15.0
41+
modulation: .fm
42+
classification: .neutral
43+
bearing_deg: none
44+
timestamp_s: 0.0
45+
}
46+
assert classify_signal(sig) == .suspected_jammer
47+
}
48+
49+
// ── Frequency band tests ────────────────────────────────────────────────
50+
51+
fn test_freq_in_band_vhf() {
52+
assert freq_in_band(150_000_000, .vhf) == true
53+
assert freq_in_band(500_000_000, .vhf) == false
54+
}
55+
56+
fn test_freq_in_band_uhf() {
57+
assert freq_in_band(1_000_000_000, .uhf) == true
58+
assert freq_in_band(30_000_000, .uhf) == false
59+
}
60+
61+
fn test_freq_bands_contiguous() {
62+
// HF ends where VHF starts
63+
assert freq_in_band(29_999_999, .hf) == true
64+
assert freq_in_band(30_000_000, .hf) == false
65+
assert freq_in_band(30_000_000, .vhf) == true
66+
}
67+
68+
// ── Distance and separation tests ───────────────────────────────────────
69+
70+
fn test_distance_squared_basic() {
71+
a := Position3D{ x: 0, y: 0, z: 0 }
72+
b := Position3D{ x: 3000, y: 4000, z: 0 }
73+
// 3^2 + 4^2 = 25 (in metres), so 3000^2 + 4000^2 = 25,000,000 mm^2
74+
assert distance_squared_mm(a, b) == 25_000_000
75+
}
76+
77+
fn test_distance_symmetric() {
78+
a := Position3D{ x: 1000, y: 2000, z: 3000 }
79+
b := Position3D{ x: -500, y: 1500, z: 0 }
80+
assert distance_squared_mm(a, b) == distance_squared_mm(b, a)
81+
}
82+
83+
fn test_ground_safe() {
84+
a := Position3D{ x: 0, y: 0, z: 0 }
85+
far := Position3D{ x: 3000, y: 0, z: 0 }
86+
close := Position3D{ x: 1000, y: 0, z: 0 }
87+
assert ground_safe(a, far) == true
88+
assert ground_safe(a, close) == false
89+
}
90+
91+
fn test_aerial_safe() {
92+
a := Position3D{ x: 0, y: 0, z: 0 }
93+
far := Position3D{ x: 15000, y: 0, z: 0 }
94+
close := Position3D{ x: 5000, y: 0, z: 0 }
95+
assert aerial_safe(a, far) == true
96+
assert aerial_safe(a, close) == false
97+
}
98+
99+
// ── Snapshot and recommendation tests ───────────────────────────────────
100+
101+
fn test_has_jamming_false() {
102+
snap := SpectrumSnapshot{
103+
centre_freq_hz: 2_400_000_000.0
104+
bandwidth_hz: 20_000_000.0
105+
noise_floor_dbm: -90.0
106+
signals: []
107+
timestamp_s: 0.0
108+
}
109+
assert has_jamming(snap) == false
110+
}
111+
112+
fn test_has_jamming_true() {
113+
snap := SpectrumSnapshot{
114+
centre_freq_hz: 2_400_000_000.0
115+
bandwidth_hz: 20_000_000.0
116+
noise_floor_dbm: -90.0
117+
signals: [
118+
DetectedSignal{
119+
frequency_hz: 2_400_000_000
120+
bandwidth_hz: 50_000_000
121+
snr_db: 40.0
122+
modulation: .cw
123+
classification: .suspected_jammer
124+
bearing_deg: none
125+
timestamp_s: 0.0
126+
},
127+
]
128+
timestamp_s: 0.0
129+
}
130+
assert has_jamming(snap) == true
131+
}
132+
133+
fn test_recommend_no_action() {
134+
snap := SpectrumSnapshot{
135+
centre_freq_hz: 150_000_000.0
136+
bandwidth_hz: 10_000_000.0
137+
noise_floor_dbm: -100.0
138+
signals: []
139+
timestamp_s: 0.0
140+
}
141+
assert recommend_response(snap) == .no_action
142+
}
143+
144+
fn test_recommend_alert_on_jammer() {
145+
snap := SpectrumSnapshot{
146+
centre_freq_hz: 2_400_000_000.0
147+
bandwidth_hz: 20_000_000.0
148+
noise_floor_dbm: -90.0
149+
signals: [
150+
DetectedSignal{
151+
frequency_hz: 2_400_000_000
152+
bandwidth_hz: 50_000_000
153+
snr_db: 40.0
154+
modulation: .cw
155+
classification: .suspected_jammer
156+
bearing_deg: none
157+
timestamp_s: 0.0
158+
},
159+
]
160+
timestamp_s: 0.0
161+
}
162+
assert recommend_response(snap) == .alert_operator
163+
}

ffi/zig/build.zig

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// SPDX-License-Identifier: PMPL-1.0-or-later
22
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
33
//
4-
// build.zig — Zig FFI bridge for Robodog ECM.
4+
// build.zig — Zig FFI bridge for Robodog ECM (Zig 0.15.x).
55
// Builds a C-ABI shared library that bridges Idris2 ABI types to the Rust core.
66

77
const std = @import("std");
@@ -11,39 +11,55 @@ pub fn build(b: *std.Build) void {
1111
const optimize = b.standardOptimizeOption(.{});
1212

1313
// Shared library for FFI consumers (Idris2, V-lang API).
14-
const lib = b.addSharedLibrary(.{
14+
const lib = b.addLibrary(.{
1515
.name = "robodog_ffi",
16-
.root_source_file = b.path("src/main.zig"),
17-
.target = target,
18-
.optimize = optimize,
16+
.root_module = b.createModule(.{
17+
.root_source_file = b.path("src/main.zig"),
18+
.target = target,
19+
.optimize = optimize,
20+
}),
21+
.linkage = .dynamic,
1922
});
2023

2124
// Also build a static library for embedding.
22-
const static_lib = b.addStaticLibrary(.{
23-
.name = "robodog_ffi",
24-
.root_source_file = b.path("src/main.zig"),
25-
.target = target,
26-
.optimize = optimize,
25+
const static_lib = b.addLibrary(.{
26+
.name = "robodog_ffi_static",
27+
.root_module = b.createModule(.{
28+
.root_source_file = b.path("src/main.zig"),
29+
.target = target,
30+
.optimize = optimize,
31+
}),
32+
.linkage = .static,
2733
});
2834

2935
b.installArtifact(lib);
3036
b.installArtifact(static_lib);
3137

3238
// Unit tests.
3339
const unit_tests = b.addTest(.{
34-
.root_source_file = b.path("src/main.zig"),
35-
.target = target,
36-
.optimize = optimize,
40+
.root_module = b.createModule(.{
41+
.root_source_file = b.path("src/main.zig"),
42+
.target = target,
43+
.optimize = optimize,
44+
}),
3745
});
3846

3947
const run_unit_tests = b.addRunArtifact(unit_tests);
4048

4149
// Integration tests.
42-
const integration_tests = b.addTest(.{
50+
const integration_mod = b.createModule(.{
4351
.root_source_file = b.path("test/integration_test.zig"),
4452
.target = target,
4553
.optimize = optimize,
4654
});
55+
integration_mod.addImport("main", b.createModule(.{
56+
.root_source_file = b.path("src/main.zig"),
57+
.target = target,
58+
.optimize = optimize,
59+
}));
60+
const integration_tests = b.addTest(.{
61+
.root_module = integration_mod,
62+
});
4763

4864
const run_integration_tests = b.addRunArtifact(integration_tests);
4965

ffi/zig/src/main.zig

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const MAX_LEGITIMATE_BW_HZ: u64 = 40_000_000;
7878
/// modulation: Modulation enum value.
7979
///
8080
/// Returns: SignalClass enum value.
81-
export fn robodog_classify_signal(
81+
pub export fn robodog_classify_signal(
8282
freq_hz: u64,
8383
bandwidth_hz: u64,
8484
snr_db: i32,
@@ -109,7 +109,7 @@ export fn robodog_classify_signal(
109109
/// Check whether a frequency falls within a given band.
110110
///
111111
/// Returns: 1 if in band, 0 if not.
112-
export fn robodog_freq_in_band(freq_hz: u64, band: i32) callconv(.c) i32 {
112+
pub export fn robodog_freq_in_band(freq_hz: u64, band: i32) callconv(.c) i32 {
113113
const b: FreqBand = @enumFromInt(band);
114114
const lower: u64 = switch (b) {
115115
.hf => 0,
@@ -130,7 +130,7 @@ export fn robodog_freq_in_band(freq_hz: u64, band: i32) callconv(.c) i32 {
130130
/// Compute squared distance between two 3D positions (in mm^2).
131131
///
132132
/// Uses integer arithmetic — no floating point, fully deterministic.
133-
export fn robodog_distance_squared_mm(
133+
pub export fn robodog_distance_squared_mm(
134134
ax: i64,
135135
ay: i64,
136136
az: i64,
@@ -147,7 +147,7 @@ export fn robodog_distance_squared_mm(
147147
/// Check ground separation safety (minimum 2.0m = 2000mm).
148148
///
149149
/// Returns: 1 if safe, 0 if violation.
150-
export fn robodog_ground_safe(
150+
pub export fn robodog_ground_safe(
151151
ax: i64,
152152
ay: i64,
153153
az: i64,
@@ -163,7 +163,7 @@ export fn robodog_ground_safe(
163163
/// Check aerial separation safety (minimum 10.0m = 10000mm).
164164
///
165165
/// Returns: 1 if safe, 0 if violation.
166-
export fn robodog_aerial_safe(
166+
pub export fn robodog_aerial_safe(
167167
ax: i64,
168168
ay: i64,
169169
az: i64,
@@ -180,7 +180,7 @@ export fn robodog_aerial_safe(
180180
///
181181
/// Writes positions to out_buf as an array of Position3D structs.
182182
/// Returns: 0 on success, -1 on invalid parameters.
183-
export fn robodog_compute_formation(
183+
pub export fn robodog_compute_formation(
184184
shape: i32,
185185
num_agents: i32,
186186
spacing_mm: u64,

0 commit comments

Comments
 (0)