diff --git a/README.md b/README.md index 48f49f9..d578358 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,10 @@ Before pressurizing anything, read the validation and safety notes in | [`docs/theory.md`](docs/theory.md) | Force-pressure equation, stroke theory, design references | | [`docs/components.md`](docs/components.md) | BOM: fittings, threads, regulators, valves, CO2, sourcing links | | [`docs/background.md`](docs/background.md) | Why artificial muscles have not gone mainstream, and what changes that | +| [`docs/test-rig-electronics.md`](docs/test-rig-electronics.md) | As-built electronics map and commissioning prerequisites for the tensile-test rig | | [`manufacturing/`](manufacturing/) | Muscle BOM, assembly process, and airtight manifold printing | | [`apps/hud/`](apps/hud/) | Zero-install browser telemetry HUD for muscle controllers (Web Serial), with a documented protocol any Arduino can speak | +| [`firmware/`](firmware/) | Safe PlatformIO commissioning baseline for the Arduino Uno R4 Minima test rig | | [`research/`](research/) | Cited research library: PAM modeling, sensing, fabrication methods, vendor surveys | | [`datasheets/`](datasheets/) | Vendor spec-sheet index with source links | diff --git a/apps/test-rig-hud/README.md b/apps/test-rig-hud/README.md new file mode 100644 index 0000000..4c827a6 --- /dev/null +++ b/apps/test-rig-hud/README.md @@ -0,0 +1,84 @@ +# AeroFlex test-rig live console + +This is the local-first operator dashboard for the AeroFlex tensile-test rig. +It shows live carriage position, commanded motion, limit switches, reference +validity, encoder diagnostics, load-cell availability, raw serial traffic, and +a rolling 60-second trend. It also provides guarded access to the existing +commissioning commands. + +The dashboard is intentionally separate from [`../hud/`](../hud/), which +targets pneumatic-muscle controllers. + +## Run it + +Use desktop Chrome or Edge. Either open [`index.html`](index.html) directly or +serve the repository root: + +```powershell +python -m http.server 8000 +``` + +Then open: + +```text +http://localhost:8000/apps/test-rig-hud/ +``` + +Close Arduino IDE, PlatformIO's serial monitor, and any other program holding +the controller's serial port. Click **Connect**, choose the Uno R4 Minima, and +wait for `PROTOCOL V1`. + +The **Demo** button runs a repeating unloaded-commissioning scenario without +hardware. It covers bottom-switch boot validation, top seek, bottom homing, +step-estimated upward travel, encoder disagreement, a refused command, and the +currently unavailable load-cell channel. + +## Safety model + +- The browser is a monitor and remote, never the safety authority. +- Motion buttons require a fresh telemetry link, an explicitly armed browser + session, an idle firmware state, and any command-specific prerequisites. +- Every motion command presents a confirmation. The **Stop** button and + `Escape` key send `x` immediately without confirmation. +- **Manual test** is a separate, typed-confirmation mode with a dedicated + press-and-hold Up/Down zone. The slider permits 10–400 pulses/s; releasing + a button sends Stop and firmware also caps a held command by time and about + 4 mm. Each manual move clears position references; it does not disable D11/D12 + directional checks or the immediate Stop path. +- The browser disarms on disconnect, stale telemetry, reload, or motion start. +- Firmware still checks D12 before every upward pulse and D11 before every + downward pulse. Bottom zero, travel bounds, and all command refusals remain + firmware-owned. +- The 24 V disconnect remains the immediate physical stop. + +Use the rig only unloaded and continuously supervised until the commissioning +work documented in [`../../firmware/README.md`](../../firmware/README.md) is +complete. + +## Data and export + +The console retains up to 30,000 telemetry, diagnostic, event, and raw serial +records in browser memory. **CSV** exports the retained session with both host +and device timestamps. Reloading the page clears the session. + +Step-position is shown only while the bottom reference is valid. Encoder travel +is labelled as a relative, uncalibrated estimate and is never substituted for +absolute carriage position. + +The force panel deliberately reads: + +```text +UNAVAILABLE — HX711 DT REPAIR REQUIRED +``` + +Firmware publishes `fs=unavail fv=0 fn=nan`; the dashboard will not infer a +force value from those fields. + +## Serial protocol + +Firmware streams a core `@T` frame at up to 5 Hz and a diagnostic `@D` frame at +up to 1 Hz. Discrete state changes use `@E` events. Unknown fields are ignored, +so protocol v1 can be extended additively. + +The dashboard can extract a limited subset of values from the older +human-readable output, but live in-motion updates require protocol v1. diff --git a/apps/test-rig-hud/index.html b/apps/test-rig-hud/index.html new file mode 100644 index 0000000..87ce3da --- /dev/null +++ b/apps/test-rig-hud/index.html @@ -0,0 +1,497 @@ + + + + + + + + + + + + +AEROFLEX // TEST-RIG LIVE CONSOLE + + + + + + +
+
+

AEROFLEX // TEST-RIG

+
LIVE COMMISSIONING CONSOLE
+
+
+ + + +
+ +
+
+
NO DEVICE CONNECTED — MOTION CONTROLS LOCKED
+
+ +
+
CARRIAGE POSITIONNOT REFERENCED
+
+
+
4003002001000 mm
+
+
TOP · D12
+
BOTTOM · D11
+
UNKNOWN
+
+
+
+
+
MM ABOVE BOTTOM · STEP ESTIMATE
+
+
+
MOTION PHASE
+
IDLE
+
DIR · NONE
+
+
+
+
0
COMMAND PULSES / S
+
0
EMITTED THIS MOVE
+
+
+
+
+ +
+
MOTION CORESTALE
+
+
DEVICE UPTIME
+
3.8000
MM / EFFECTIVE REV
+
0
REQUESTED PULSES
+
0
RETAINED SAMPLES
+
+
+ +
+
LIMITS & REFERENCESACTIVE LOW
+
TOP SWITCH · D12RELEASED
+
BOTTOM SWITCH · D11RELEASED
+
BOTTOM BOOT SAFETYNOT PASSED
+
BOTTOM ZERO REFERENCENOT SET
+
TOP REFERENCENOT SET
+
+ +
+
ENCODER DIAGNOSTICUNCALIBRATED
+
+
A · GRAY D3LOW0 EDGES
+
B · WHITE D2LOW0 EDGES
+
+
+
0
SIGNED QUADRATURE COUNT
+
0.000
RELATIVE ESTIMATE · NOT POSITION
+
0
INVALID TRANSITIONS
+
ABS ENCODER / COMMAND
+
+
+ +
+
TENSILE FORCENO SIGNAL
+
+ UNAVAILABLE — HX711 DT REPAIR REQUIRED +
Force fields are present in telemetry but intentionally invalid. No load-cell value is inferred or displayed until the cut data wire is repaired and the channel is calibrated.
+
+
+
FORCE · N
+
SESSION PEAK · N
+
+
+ +
+
SUPERVISED CONTROLDISARMED
+
+ + + + + + + + + +
+
+ +
Arming only unlocks the browser controls. Firmware limits and references remain authoritative.
+
+
+ +
This gate unlocks the dedicated press-and-hold zone below. D11/D12 checks and Stop cannot be disabled.
+
+
+
MANUAL MOTOR ZONEHOLD TO MOVE
+
Press and hold a direction to move; release anywhere to send Stop. Use 10 PPS crawl mode to watch the marked motor shaft. Firmware applies both a 10-second and approximately 4 mm cap.
+ +
+ + +
+
References clear at move start. For diagnosis, mark the motor shaft and coupling; physical directional-limit checks and the 24 V disconnect remain authoritative.
+
+
+ + +
+
+ + + +
+
+ 60 SECOND TREND +
STEP MMENCODER REL MMFORCE N
+
+
+
+
+ + + + + + diff --git a/apps/test-rig-hud/og.png b/apps/test-rig-hud/og.png new file mode 100644 index 0000000..2009299 Binary files /dev/null and b/apps/test-rig-hud/og.png differ diff --git a/apps/test-rig-hud/site/.gitignore b/apps/test-rig-hud/site/.gitignore new file mode 100644 index 0000000..3be4a7b --- /dev/null +++ b/apps/test-rig-hud/site/.gitignore @@ -0,0 +1,2 @@ +dist/ +*.tgz diff --git a/apps/test-rig-hud/site/.openai/hosting.json b/apps/test-rig-hud/site/.openai/hosting.json new file mode 100644 index 0000000..3a664ce --- /dev/null +++ b/apps/test-rig-hud/site/.openai/hosting.json @@ -0,0 +1,3 @@ +{ + "project_id": "appgprj_6a691831828c819199c046b212dd8330" +} diff --git a/apps/test-rig-hud/site/README.md b/apps/test-rig-hud/site/README.md new file mode 100644 index 0000000..30d6a28 --- /dev/null +++ b/apps/test-rig-hud/site/README.md @@ -0,0 +1,9 @@ +# Hosted console wrapper + +This dependency-free build embeds the adjacent standalone dashboard and social +preview into a Cloudflare Worker-compatible ESM entrypoint for private Sites +deployment. + +```powershell +npm run build +``` diff --git a/apps/test-rig-hud/site/build.mjs b/apps/test-rig-hud/site/build.mjs new file mode 100644 index 0000000..58c8cf2 --- /dev/null +++ b/apps/test-rig-hud/site/build.mjs @@ -0,0 +1,62 @@ +import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const here = dirname(fileURLToPath(import.meta.url)); +const dashboardPath = resolve(here, "..", "index.html"); +const socialCardPath = resolve(here, "..", "og.png"); +const outputDirectory = resolve(here, "dist", "server"); +const outputPath = resolve(outputDirectory, "index.js"); + +const dashboard = await readFile(dashboardPath, "utf8"); +const socialCard = (await readFile(socialCardPath)).toString("base64"); + +const worker = ` +const dashboard = ${JSON.stringify(dashboard)}; +const socialCardBase64 = ${JSON.stringify(socialCard)}; +let socialCardBytes; + +function decodeSocialCard() { + if (socialCardBytes) return socialCardBytes; + const binary = atob(socialCardBase64); + socialCardBytes = Uint8Array.from(binary, character => character.charCodeAt(0)); + return socialCardBytes; +} + +export default { + async fetch(request) { + const url = new URL(request.url); + if (url.pathname === "/" || url.pathname === "/index.html" || url.pathname === "/console.html") { + const absoluteCard = new URL("/og.png", url.origin).toString(); + const html = dashboard.replace( + '', + '' + ); + return new Response(request.method === "HEAD" ? null : html, { + headers: { + "Content-Type": "text/html; charset=utf-8", + "Cache-Control": "no-cache", + "Permissions-Policy": "serial=(self)", + "X-Content-Type-Options": "nosniff", + "Referrer-Policy": "no-referrer" + } + }); + } + if (url.pathname === "/og.png") { + return new Response(request.method === "HEAD" ? null : decodeSocialCard(), { + headers: { + "Content-Type": "image/png", + "Cache-Control": "public, max-age=86400, immutable", + "X-Content-Type-Options": "nosniff" + } + }); + } + return new Response("Not found", { status: 404 }); + } +}; +`; + +await rm(resolve(here, "dist"), { recursive: true, force: true }); +await mkdir(outputDirectory, { recursive: true }); +await writeFile(outputPath, worker, "utf8"); +console.log(`Built ${outputPath}`); diff --git a/apps/test-rig-hud/site/package-lock.json b/apps/test-rig-hud/site/package-lock.json new file mode 100644 index 0000000..b44fa12 --- /dev/null +++ b/apps/test-rig-hud/site/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "aeroflex-test-rig-live-console", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "aeroflex-test-rig-live-console", + "version": "1.0.0" + } + } +} diff --git a/apps/test-rig-hud/site/package.json b/apps/test-rig-hud/site/package.json new file mode 100644 index 0000000..15aa987 --- /dev/null +++ b/apps/test-rig-hud/site/package.json @@ -0,0 +1,10 @@ +{ + "name": "aeroflex-test-rig-live-console", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "node build.mjs", + "test": "npm run build && node test.mjs" + } +} diff --git a/apps/test-rig-hud/site/test.mjs b/apps/test-rig-hud/site/test.mjs new file mode 100644 index 0000000..0ae9794 --- /dev/null +++ b/apps/test-rig-hud/site/test.mjs @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +const dashboard = await readFile(new URL("../index.html", import.meta.url), "utf8"); +const scriptMatch = dashboard.match(/