297 lines
16 KiB
JavaScript
297 lines
16 KiB
JavaScript
// HOMECORE-UI contract-conformant mock layer — ADR-131 §7.1.
|
|
//
|
|
// "Where a service is not yet stable, the panel is still built against
|
|
// its defined contract (with a contract-conformant mock standing in for
|
|
// the live endpoint only until that endpoint lands)."
|
|
//
|
|
// Shapes mirror the schemas described in ADR-131 §4 + the calibration
|
|
// RoomState contract (docs/integration/calibration-appliance-integration.md)
|
|
// + the SEED HTTPS API. Live endpoints replace these the moment they
|
|
// exist; nothing here is presented to the operator as real (the UI shows
|
|
// a DEMO badge whenever the mock layer is serving a panel — see api.js).
|
|
|
|
const now = () => new Date().toISOString();
|
|
const ago = (s) => new Date(Date.now() - s * 1000).toISOString();
|
|
function jitter(base, amp) { return +(base + (Math.sin(Date.now() / 3000 + base) * amp)).toFixed(2); }
|
|
function spark(base, amp, n = 24) {
|
|
return Array.from({ length: n }, (_, i) => +(base + Math.sin(i / 2) * amp + (i % 3) * amp * 0.2).toFixed(2));
|
|
}
|
|
|
|
// Factory for a bedroom SEED node — keeps the three bedrooms consistent
|
|
// while varying the values that matter for the analysis views.
|
|
function bedroomSeed(o) {
|
|
return {
|
|
device_id: o.device_id, firmware: '0.7.3', online: true, conn: o.conn || 'wifi', epoch: o.epoch,
|
|
vector_count: o.vector_count, vector_dim: 8, knn_latency_ms: o.knn_latency_ms,
|
|
last_ingest: ago(2), witness_valid: true, witness_len: o.witness_len,
|
|
witness_last_verify: ago(1800), zone: o.zone,
|
|
storage_used: o.vector_count, storage_budget: 100000,
|
|
sensors: {
|
|
bme280: { temp_c: o.temp_c, humidity_pct: o.humidity_pct, pressure_hpa: 1013.0 },
|
|
pir: { motion: o.motion, last_trigger: ago(o.motion ? 5 : 640) },
|
|
reed: { open: false, last_change: ago(30000) },
|
|
ads1115: [{ label: 'ch0', v: 0.11 }, { label: 'ch1', v: 0.0 }, { label: 'ch2', v: 0.0 }, { label: 'ch3', v: 0.0 }],
|
|
vibration: { active: false, last_trigger: null },
|
|
},
|
|
reflex: [
|
|
{ name: 'fragility_alarm', threshold: 0.3, target: 'relay actuator', last_fired: o.fired ? ago(420) : null, fired_recently: !!o.fired },
|
|
{ name: 'drift_cutoff', threshold: 1.0, target: 'ingest gate', last_fired: null, fired_recently: false },
|
|
{ name: 'hd_anomaly_indicator', threshold: 200, target: 'PWM brightness', last_fired: null, fired_recently: false },
|
|
],
|
|
cognition: { fragility: o.fragility, coherence_phases: o.phases, knn_rebuild_s: 10 },
|
|
ingest: { batch: 64, flush_ms: 1000, bridge: 'direct', esp32: [{ node_id: o.node, packet: '0xC5110003', rate_hz: 1.0 }] },
|
|
esp32_nodes: 1, frame_rate_hz: 100,
|
|
};
|
|
}
|
|
|
|
// ── v0 Appliance health (§4.1) ──────────────────────────────────────
|
|
export function applianceHealth() {
|
|
return {
|
|
cpu_pct: jitter(34, 6),
|
|
ram_pct: jitter(58, 4),
|
|
hailo_load_pct: jitter(41, 12),
|
|
hailo_temp_c: jitter(52, 3),
|
|
uptime_s: 824510,
|
|
services: [
|
|
{ name: 'ruview-mcp-brain', port: 9876, status: 'running' },
|
|
{ name: 'cognitum-rvf-agent', port: 9004, status: 'running' },
|
|
{ name: 'ruvector-hailo-worker', port: 50051, status: 'running' },
|
|
],
|
|
event_rate: spark(120, 40),
|
|
channel_capacity: 4096,
|
|
channel_lag: 0,
|
|
};
|
|
}
|
|
|
|
// ── SEED fleet (§4.1 / §4.2) ────────────────────────────────────────
|
|
const SEEDS = [
|
|
{
|
|
device_id: 'seed-livingroom-a1',
|
|
firmware: '0.7.3', online: true, conn: 'wifi', epoch: 184,
|
|
vector_count: 71280, vector_dim: 8, knn_latency_ms: 2.1,
|
|
last_ingest: ago(3), witness_valid: true, witness_len: 184210,
|
|
witness_last_verify: ago(900), zone: 'Living Room',
|
|
storage_used: 71280, storage_budget: 100000,
|
|
sensors: {
|
|
bme280: { temp_c: 21.6, humidity_pct: 44, pressure_hpa: 1013.2 },
|
|
pir: { motion: true, last_trigger: ago(8) },
|
|
reed: { open: false, last_change: ago(7200) },
|
|
ads1115: [{ label: 'soil', v: 0.42 }, { label: 'light', v: 0.71 }, { label: 'aux2', v: 0.03 }, { label: 'aux3', v: 0.0 }],
|
|
vibration: { active: false, last_trigger: ago(40000) },
|
|
},
|
|
reflex: [
|
|
{ name: 'fragility_alarm', threshold: 0.3, target: 'relay actuator', last_fired: ago(300), fired_recently: true },
|
|
{ name: 'drift_cutoff', threshold: 1.0, target: 'ingest gate', last_fired: null, fired_recently: false },
|
|
{ name: 'hd_anomaly_indicator', threshold: 200, target: 'PWM brightness', last_fired: ago(12000), fired_recently: false },
|
|
],
|
|
cognition: { fragility: 0.42, coherence_phases: [{ t: ago(3600), label: 'empty' }, { t: ago(1800), label: 'occupied' }, { t: ago(300), label: 'regime-change' }], knn_rebuild_s: 10 },
|
|
ingest: { batch: 64, flush_ms: 1000, bridge: 'host-laptop hop', esp32: [{ node_id: 'esp32-lr-01', packet: '0xC5110003', rate_hz: 1.0 }, { node_id: 'esp32-lr-02', packet: '0xC5110002', rate_hz: 0.9 }] },
|
|
esp32_nodes: 2, frame_rate_hz: 98,
|
|
},
|
|
bedroomSeed({
|
|
device_id: 'seed-bedroom-1', zone: 'Bedroom 1 (primary)', epoch: 183,
|
|
vector_count: 38110, knn_latency_ms: 1.7, witness_len: 91022,
|
|
temp_c: 20.1, humidity_pct: 47, motion: false, fragility: 0.12,
|
|
phases: [{ t: ago(7200), label: 'empty' }, { t: ago(3600), label: 'sleep' }],
|
|
node: 'esp32-br1-01', conn: 'usb',
|
|
}),
|
|
bedroomSeed({
|
|
device_id: 'seed-bedroom-2', zone: 'Bedroom 2 (guest)', epoch: 181,
|
|
vector_count: 29440, knn_latency_ms: 1.9, witness_len: 70210,
|
|
temp_c: 19.4, humidity_pct: 50, motion: true, fragility: 0.21,
|
|
phases: [{ t: ago(5400), label: 'empty' }, { t: ago(900), label: 'occupied' }],
|
|
node: 'esp32-br2-01', conn: 'wifi',
|
|
}),
|
|
bedroomSeed({
|
|
device_id: 'seed-bedroom-3', zone: 'Bedroom 3 (kids)', epoch: 179,
|
|
vector_count: 24105, knn_latency_ms: 2.0, witness_len: 60880,
|
|
temp_c: 21.0, humidity_pct: 45, motion: false, fragility: 0.34,
|
|
phases: [{ t: ago(9000), label: 'empty' }, { t: ago(4200), label: 'sleep' }, { t: ago(600), label: 'restless' }],
|
|
node: 'esp32-br3-01', conn: 'wifi', fired: true,
|
|
}),
|
|
{
|
|
device_id: 'seed-hallway-c3',
|
|
firmware: '0.6.9', online: false, conn: 'wifi', epoch: 170,
|
|
vector_count: 12044, vector_dim: 8, knn_latency_ms: null,
|
|
last_ingest: ago(5400), witness_valid: true, witness_len: 40110,
|
|
witness_last_verify: ago(86400), zone: 'Hallway',
|
|
storage_used: 12044, storage_budget: 100000,
|
|
sensors: null,
|
|
reflex: [],
|
|
cognition: { fragility: null, coherence_phases: [], knn_rebuild_s: 10 },
|
|
ingest: { batch: 64, flush_ms: 1000, bridge: 'direct', esp32: [] },
|
|
esp32_nodes: 0, frame_rate_hz: 0,
|
|
warnings: ['stale firmware version (0.6.9 < 0.7.3)', 'offline > 1h'],
|
|
},
|
|
];
|
|
export function seeds() { return SEEDS.map((s) => ({ ...s })); }
|
|
export function seed(id) { return SEEDS.find((s) => s.device_id === id) || null; }
|
|
|
|
// ── ESP32 node warnings (§4.1) ──────────────────────────────────────
|
|
export function esp32Warnings() {
|
|
return [
|
|
{ node_id: 'esp32-lr-02', seed: 'seed-livingroom-a1', issue: 'presence_score normalisation anomaly' },
|
|
{ node_id: 'esp32-hw-01', seed: 'seed-hallway-c3', issue: 'stale firmware version' },
|
|
];
|
|
}
|
|
|
|
// ── COG runtime (§4.6) ──────────────────────────────────────────────
|
|
const COGS = [
|
|
{ id: 'cog-ha-matter', version: '1.4.2', arch: 'arm', status: 'running', pid: 4120, sha256_verified: true, signature_verified: true },
|
|
{ id: 'cog-pose-estimation', version: '2.1.0', arch: 'hailo10', status: 'running', pid: 4188, sha256_verified: true, signature_verified: true, hef: ['rf_foundation_encoder.hef', 'pose_head.hef'], throughput_fps: 41 },
|
|
{ id: 'cog-person-count', version: '0.9.4', arch: 'arm', status: 'running', pid: 4205, sha256_verified: true, signature_verified: true },
|
|
{ id: 'cog-calibration', version: '1.0.1', arch: 'arm', status: 'running', pid: 4250, sha256_verified: true, signature_verified: true },
|
|
{ id: 'cog-anomaly-watch', version: '0.3.0', arch: 'arm', status: 'failed', pid: null, sha256_verified: true, signature_verified: true, error: 'panic: bank not found' },
|
|
{ id: 'cog-legacy-bridge', version: '0.1.2', arch: 'arm', status: 'stopped', pid: null, sha256_verified: false, signature_verified: false },
|
|
];
|
|
export function cogs() { return COGS.map((c) => ({ ...c })); }
|
|
export function cogUpdates() { return [{ id: 'cog-pose-estimation', from: '2.1.0', to: '2.2.0', new_entities: ['sensor.lr_pose_confidence'], config_changes: ['add: max_persons'] }]; }
|
|
export function appRegistry() {
|
|
return [
|
|
{ id: 'cog-fall-detect', title: 'Fall Detection', desc: 'Multistatic fall detection specialist', category: 'safety', arch: 'arm', featured: true, new_entities: ['binary_sensor.{room}_fall'] },
|
|
{ id: 'cog-sleep-stage', title: 'Sleep Staging', desc: 'REM/deep/light from breathing + restlessness', category: 'health', arch: 'hailo10', new_entities: ['sensor.{room}_sleep_stage'] },
|
|
{ id: 'cog-gesture', title: 'Gesture Control', desc: 'DTW gesture classifier → service calls', category: 'control', arch: 'arm', new_entities: ['event.{room}_gesture'] },
|
|
];
|
|
}
|
|
|
|
// ── RoomState / sensing (§4.5) — calibration contract ───────────────
|
|
export function roomStates() {
|
|
return [
|
|
{
|
|
room_id: 'living_room', stale: false, vetoed: false, seeds: ['seed-livingroom-a1'],
|
|
presence: { value: 'occupied', confidence: 0.93 },
|
|
posture: { value: 'sitting', confidence: 0.81 },
|
|
breathing_bpm: { value: jitter(15, 1.5), confidence: 0.77 },
|
|
heart_bpm: { value: jitter(72, 3), confidence: 0.64 },
|
|
restlessness: { value: 0.22, confidence: 0.7 },
|
|
anomaly: { value: 0.18, confidence: 0.8, threshold: 0.8 },
|
|
},
|
|
{
|
|
// Bedroom 1 — primary; healthy sleeping vitals.
|
|
room_id: 'bedroom_1', stale: false, vetoed: false, seeds: ['seed-bedroom-1'],
|
|
presence: { value: 'occupied', confidence: 0.91 },
|
|
posture: { value: 'lying', confidence: 0.9 },
|
|
breathing_bpm: { value: jitter(12, 1), confidence: 0.85 },
|
|
heart_bpm: { value: jitter(58, 2), confidence: 0.72 },
|
|
restlessness: { value: 0.08, confidence: 0.8 },
|
|
anomaly: { value: 0.12, confidence: 0.84, threshold: 0.8 },
|
|
},
|
|
{
|
|
// Bedroom 2 — guest; STALE bank (recalibrate demo).
|
|
room_id: 'bedroom_2', stale: true, vetoed: false, seeds: ['seed-bedroom-2'],
|
|
presence: { value: 'occupied', confidence: 0.86 },
|
|
posture: { value: 'sitting', confidence: 0.7 },
|
|
breathing_bpm: { value: jitter(16, 1.5), confidence: 0.66 },
|
|
heart_bpm: { value: jitter(74, 3), confidence: 0.58 },
|
|
restlessness: { value: 0.31, confidence: 0.62 },
|
|
anomaly: { value: 0.4, confidence: 0.6, threshold: 0.8 },
|
|
},
|
|
{
|
|
// Bedroom 3 — kids; heartbeat specialist not yet trained.
|
|
room_id: 'bedroom_3', stale: false, vetoed: false, seeds: ['seed-bedroom-3'],
|
|
presence: { value: 'occupied', confidence: 0.79 },
|
|
posture: { value: 'lying', confidence: 0.74 },
|
|
breathing_bpm: { value: jitter(18, 2), confidence: 0.69 },
|
|
heart_bpm: null, // null = not trained (§6 invariant 3)
|
|
restlessness: { value: 0.46, confidence: 0.6 },
|
|
anomaly: { value: 0.22, confidence: 0.7, threshold: 0.8 },
|
|
},
|
|
{
|
|
room_id: 'kitchen', stale: false, vetoed: true, seeds: ['seed-livingroom-a1', 'seed-hallway-c3'],
|
|
presence: { value: 'occupied', confidence: 0.6 },
|
|
posture: { value: null, confidence: null }, // suppressed by veto — withheld, NOT zero (§4.5)
|
|
breathing_bpm: { value: null, confidence: null },
|
|
heart_bpm: { value: null, confidence: null },
|
|
restlessness: { value: 0.4, confidence: 0.5 },
|
|
anomaly: { value: 0.91, confidence: 0.88, threshold: 0.8 },
|
|
},
|
|
{
|
|
room_id: 'office', stale: false, vetoed: false, seeds: ['seed-bedroom-1'],
|
|
presence: { value: 'absent', confidence: 0.95 },
|
|
posture: null, // null = not trained (§6 invariant 3)
|
|
breathing_bpm: null,
|
|
heart_bpm: null,
|
|
restlessness: { value: 0.0, confidence: 0.9 },
|
|
anomaly: { value: 0.05, confidence: 0.9, threshold: 0.8 },
|
|
},
|
|
];
|
|
}
|
|
|
|
// ── Fleet map / federation (§4.3) ───────────────────────────────────
|
|
export function federation() {
|
|
return {
|
|
coordinator: 'seed-livingroom-a1', round: 47, k_healthy: 4, delta_status: 'exchanging',
|
|
invariant: 'model deltas only — never raw CSI',
|
|
krum: { f: 1, multi: true }, cadence_min: 30,
|
|
mesh_links: [
|
|
{ a: 'seed-livingroom-a1', b: 'seed-bedroom-1', health: 'green' },
|
|
{ a: 'seed-bedroom-1', b: 'seed-bedroom-2', health: 'green' },
|
|
{ a: 'seed-bedroom-2', b: 'seed-bedroom-3', health: 'amber' },
|
|
{ a: 'seed-bedroom-1', b: 'seed-hallway-c3', health: 'red' },
|
|
],
|
|
fused_events: [{ kind: 'fall', seeds: ['seed-livingroom-a1', 'seed-hallway-c3'], n: 2 }, { kind: 'occupant-track', seeds: ['seed-bedroom-1', 'seed-bedroom-2', 'seed-livingroom-a1'], n: 3 }],
|
|
};
|
|
}
|
|
|
|
// ── Witness / audit (§4.9) ──────────────────────────────────────────
|
|
export function witnessLog(page = 0, size = 12) {
|
|
const total = 240;
|
|
const items = Array.from({ length: size }, (_, i) => {
|
|
const n = page * size + i;
|
|
const seedTier = n % 2 === 0;
|
|
return {
|
|
entity_id: seedTier ? `rvf.store.write.${184210 - n}` : ['sensor.living_room_presence', 'binary_sensor.front_door', 'sensor.bedroom_breathing_rate'][n % 3],
|
|
old_state: seedTier ? null : ['false', 'off', '14.5'][n % 3],
|
|
new_state: seedTier ? `sha256:${(0x9a3f + n).toString(16)}…` : ['true', 'on', '15.1'][n % 3],
|
|
ts: ago(n * 37),
|
|
tier: seedTier ? 'seed-sha256' : 'homecore-ed25519',
|
|
seed: ['seed-livingroom-a1', 'seed-bedroom-1', 'seed-bedroom-2', 'seed-bedroom-3'][n % 4],
|
|
key_fp: ['a1b2c3d4', 'e5f6a7b8', 'c9d0e1f2', 'b3a4c5d6'][n % 4],
|
|
};
|
|
});
|
|
return { items, page, size, total };
|
|
}
|
|
export function privacyModes() {
|
|
return [
|
|
{ seed: 'seed-livingroom-a1', mode: 'full-publish' },
|
|
{ seed: 'seed-bedroom-1', mode: 'audit-only' },
|
|
{ seed: 'seed-bedroom-2', mode: 'audit-only' },
|
|
{ seed: 'seed-bedroom-3', mode: 'audit-only' },
|
|
{ seed: 'seed-hallway-c3', mode: 'audit-only' },
|
|
];
|
|
}
|
|
|
|
// ── Events / automations (§4.8) ─────────────────────────────────────
|
|
export function recentEvents(n = 40) {
|
|
const variants = ['StateChanged', 'EntityRegistered', 'ConfigReloaded'];
|
|
const ents = ['sensor.living_room_presence', 'binary_sensor.front_door', 'light.kitchen_ceiling', 'sensor.bedroom_breathing_rate'];
|
|
return Array.from({ length: n }, (_, i) => ({
|
|
type: variants[i % 3],
|
|
entity_id: ents[i % ents.length],
|
|
old_state: ['off', 'false', '14.5'][i % 3],
|
|
new_state: ['on', 'true', '15.1'][i % 3],
|
|
ts: ago(i * 11),
|
|
user_id: i % 4 === 0 ? 'operator' : null,
|
|
context: { id: 'ctx-' + (1000 + i), parent_id: i % 3 === 0 ? 'ctx-' + (999 + i) : null, grandparent_id: i % 6 === 0 ? 'ctx-' + (998 + i) : null },
|
|
source: ['seed-livingroom-a1', 'cog-ha-matter'][i % 2],
|
|
}));
|
|
}
|
|
|
|
// ── Settings (§4.10) ────────────────────────────────────────────────
|
|
export function settings() {
|
|
return {
|
|
mqtt: { broker: 'mqtt://cognitum-v0:1883', user: 'homecore', mdns: '_ruview-ha._tcp', connected: true },
|
|
tokens: [
|
|
{ name: 'ios-companion', last_used: ago(120), created: ago(8000000) },
|
|
{ name: 'node-red', last_used: ago(60000), created: ago(20000000) },
|
|
],
|
|
ha_disco_entities: 21,
|
|
esp32: [
|
|
{ node_id: 'esp32-lr-01', ip: '192.168.1.31', port: 5566, firmware: '1.2.0', room: 'living_room', seed: 'seed-livingroom-a1' },
|
|
{ node_id: 'esp32-br1-01', ip: '192.168.1.32', port: 5566, firmware: '1.2.0', room: 'bedroom_1', seed: 'seed-bedroom-1' },
|
|
{ node_id: 'esp32-br2-01', ip: '192.168.1.33', port: 5566, firmware: '1.2.0', room: 'bedroom_2', seed: 'seed-bedroom-2' },
|
|
{ node_id: 'esp32-br3-01', ip: '192.168.1.34', port: 5566, firmware: '1.2.0', room: 'bedroom_3', seed: 'seed-bedroom-3' },
|
|
],
|
|
};
|
|
}
|