Skip to content
Closed
Show file tree
Hide file tree
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
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,29 @@ jobs:
node-version: 22
cache: pnpm

- id: example-versions
name: Read fixed example dependencies
run: |
node --input-type=module <<'JS'
import {readFileSync, appendFileSync} from 'node:fs';
const {components} = JSON.parse(readFileSync('data/reference/versions.json'));
appendFileSync(process.env.GITHUB_OUTPUT, `cpp_ref=${components['netft-cpp'].sourceCommit}\npynetft_version=${components.pyNetFT.version}\n`);
JS

- name: Obtain the fixed C++ SDK for examples
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
repository: netft/netft-cpp
ref: ${{ steps.example-versions.outputs.cpp_ref }}
path: .dependencies/netft-cpp
persist-credentials: false

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'

- name: Install the fixed Python package for examples
run: python3 -m pip install 'pynetft==${{ steps.example-versions.outputs.pynetft_version }}'

- run: pnpm install --frozen-lockfile
- run: pnpm check
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ Thumbs.db

# Local development plans
.superpowers/

.dependencies/
1 change: 1 addition & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"**/*.mdx",
"!node_modules/**",
"!build/**",
"!.dependencies/**",
"!.superpowers/**",
],
}
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ Tests should cover data contracts, validators, generated routes, links, and othe
Keep each pull request focused. Explain the reader-facing outcome, source repositories checked, validation run, and any accessibility or deployment impact.

By contributing authored content under `docs/`, you agree to license that contribution under [Creative Commons Attribution 4.0 International](LICENSE-DOCS). Contributions to code, examples, tests, configuration, scripts, and site tooling are licensed under the [Apache License 2.0](LICENSE). Do not submit third-party material unless its terms permit inclusion and its original license is preserved.

## Example dependencies

Example checks are required by default: `NETFT_CPP_ROOT` must point to SDK headers (the default is `.dependencies/netft-cpp`), and `PYTHON` must import the version of pynetft in `data/reference/versions.json`. CI obtains the fixed SDK commit and Python release explicitly. `pnpm check:examples --allow-missing` is a local opt-in for working on prose without installed dependencies; skipped checks are not passes. Imports load the Python example without invoking `main()` or contacting hardware.

## Reference provenance

`pnpm update:references -- --cpp PATH --python PATH --cli PATH --ros PATH` requires clean checkouts whose origin, HEAD, exact tag and project version match `data/reference/versions.json`. CLI extraction consumes `--schema` and also verifies executable version, commit and clean-source identity; point `NETFT_CLI_EXECUTABLE` at the matching build. Older CLI releases without this interface cannot regenerate their CLI manifest with the new extractor. Their existing published reference remains pinned to its real release commit.

To review an unreleased candidate, copy the versions file to a temporary file, set each entry's `publication` and `sourceTag` to `unreleased`, set `sourceCommit` to its exact clean checkout HEAD, and use a `sourceUrl` ending in `/tree/<commit>`. Set `NETFT_REFERENCE_VERSIONS` and `NETFT_REFERENCE_OUTPUT` to the temporary metadata file and an existing temporary directory. Review generated candidates before explicitly replacing published references; do not label candidate code as a released tag.
4 changes: 3 additions & 1 deletion data/reference/cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -283,5 +283,7 @@
"code": 130,
"meaning": "Interrupted"
}
]
],
"sourceCommit": "ea7d21d614fdee86a34cadf2a0b456158ecfa41b",
"publication": "released"
}
4 changes: 3 additions & 1 deletion data/reference/cpp.json
Original file line number Diff line number Diff line change
Expand Up @@ -1084,5 +1084,7 @@
"values": [],
"bases": []
}
]
],
"sourceCommit": "3259b8576b16fb150b51e9ef9090366c5e6efcba",
"publication": "released"
}
4 changes: 3 additions & 1 deletion data/reference/python.json
Original file line number Diff line number Diff line change
Expand Up @@ -1020,5 +1020,7 @@
],
"bases": ["str", "Enum"]
}
]
],
"sourceCommit": "7ed2e2061f942d963116d78f3de76b4680e6fc44",
"publication": "released"
}
4 changes: 3 additions & 1 deletion data/reference/ros.json
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,7 @@
"type": "double",
"description": "ros2_control force/torque state interface"
}
]
],
"sourceCommit": "ed23dc848f9ba6652d693f56341cca2b8480a8f2",
"publication": "released"
}
15 changes: 10 additions & 5 deletions data/reference/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,36 @@
"version": "0.3.3",
"sourceTag": "v0.3.3",
"repository": "https://github.com/netft/netft-cpp",
"sourceUrl": "https://github.com/netft/netft-cpp/tree/v0.3.3"
"sourceUrl": "https://github.com/netft/netft-cpp/tree/v0.3.3",
"sourceCommit": "3259b8576b16fb150b51e9ef9090366c5e6efcba"
},
"pyNetFT": {
"version": "2.1.1",
"sourceTag": "v2.1.1",
"repository": "https://github.com/netft/pyNetFT",
"sourceUrl": "https://github.com/netft/pyNetFT/tree/v2.1.1"
"sourceUrl": "https://github.com/netft/pyNetFT/tree/v2.1.1",
"sourceCommit": "7ed2e2061f942d963116d78f3de76b4680e6fc44"
},
"netft-cli": {
"version": "0.2.1",
"sourceTag": "v0.2.1",
"repository": "https://github.com/netft/netft-cli",
"sourceUrl": "https://github.com/netft/netft-cli/tree/v0.2.1"
"sourceUrl": "https://github.com/netft/netft-cli/tree/v0.2.1",
"sourceCommit": "ea7d21d614fdee86a34cadf2a0b456158ecfa41b"
},
"ros-netft": {
"version": "0.3.3",
"sourceTag": "0.3.3",
"repository": "https://github.com/netft/ros-netft",
"sourceUrl": "https://github.com/netft/ros-netft/tree/0.3.3"
"sourceUrl": "https://github.com/netft/ros-netft/tree/0.3.3",
"sourceCommit": "ed23dc848f9ba6652d693f56341cca2b8480a8f2"
},
"netft-viewer": {
"version": "0.1.1",
"sourceTag": "v0.1.1",
"repository": "https://github.com/netft/netft-viewer",
"sourceUrl": "https://github.com/netft/netft-viewer/tree/v0.1.1"
"sourceUrl": "https://github.com/netft/netft-viewer/tree/v0.1.1",
"sourceCommit": "9929c63f4822771a417f32b91c33ce8d6344d642"
}
}
}
16 changes: 16 additions & 0 deletions docs/references/about.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,19 @@ Hardware guidance in this site is based on the following ATI Industrial Automati
<OfficialSources />

ATI material is authoritative for physical installation, electrical limits, model-specific axes, calibration, firmware, filters, transformations, and persistent device settings. This site paraphrases and links to that material; it does not redistribute ATI manuals.

## Maintenance and issue routing

The organization has seven repositories: five runtime components, `netft-docs`, and `.github`. The component inventory and supported distribution metadata are maintained in `data/ecosystem.json`; each component owns its source, release checks and version-specific support contract.

| Concern | Owning repository |
| ------------------------------------------------ | ----------------- |
| RDT protocol, calibration, native recovery | `netft-cpp` |
| Python delivery and compatibility API | `pyNetFT` |
| Terminal parsing, diagnostics, recording | `netft-cli` |
| ROS nodes, controllers, distribution integration | `ros-netft` |
| Desktop interaction, companion, packaging | `netft-viewer` |
| Shared user guides and source references | `netft-docs` |
| Organization navigation and policies | `.github` |

Fix shared core behavior upstream, then verify each consumer's pinned source manifest and adaptations. The compatibility page uses the maintained component inventory; consult the ROS repository for the exact distribution contract and validation gaps. Candidate documentation explicitly identifies unreleased behavior.
20 changes: 20 additions & 0 deletions docs/references/data-formats/csv.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,23 @@ force_unit,torque_unit
`host_receive_timestamp_ns` uses the system clock. `elapsed_recording_time_ns` is monotonic from recording start. Configuration revision and unit columns keep calibration changes visible across reconnects.

CLI and Viewer recorders write a temporary `.partial` file and promote it only after a clean queue drain and flush. A partial file is not proof of a complete capture.

## Recording metadata (unreleased candidate)

After successful data finalization, the candidate writes `<output>.metadata.json` using schema version 1 and kind `netft-recording`. CLI and Viewer share these meanings:

| Field | Meaning |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `producer`, `result` | `netft-cli` or `netft-viewer`; `complete` or CLI `interrupted` after a successful nonempty drain. |
| `accepted_samples`, `written_samples` | Samples accepted into the recording queue and rows successfully written. These are not power-loss durability guarantees. |
| `sample_span_seconds` | Nonnegative host receive-time span between first and last written sample; zero for fewer than two samples. |
| `configuration_revisions` | Unique revision identifiers in written rows, scoped to the producer session. They do not contain full calibration snapshots. |
| `force_units`, `torque_units` | Unique native unit symbols observed in written rows. |
| `recorded_rdt_gaps` | Sum of forward sequence gaps between written rows, using unsigned wraparound and ignoring backward jumps. Pauses, rate filtering and reconnects can contribute; this is not the SDK packet-loss counter. |
| `pause_count` | Accepted Viewer pauses; CLI has no pause operation and reports zero. |
| `reconnect_count` | CLI SDK reconnect counter for the run; `null` for Viewer because its recorder does not own connection health. |
| `error` | `null` on successful finalization. Failure details remain in the CLI error or Viewer recorder state. |

Partial or failed captures do not receive a completed summary. Metadata creation and promotion follow the data file's overwrite policy. The two files are finalized separately: a metadata failure reports an error and preserves the completed data file, and may leave a metadata partial for recovery. Keep both files when archiving a successful capture.

The CLI candidate appends `configuration_revision` to CSV without moving existing columns and adds it inside the NDJSON sample object. Viewer already records revisions in each CSV row. Existing published releases retain their documented formats until these changes are released.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"serve": "docusaurus serve",
"start": "docusaurus start",
"test": "node --test",
"test:unit": "node --test test/check-built-site.test.mjs test/validate-ecosystem.test.mjs test/validate-sources.test.mjs test/reference-manifest.test.mjs test/reference-update.test.mjs",
"test:unit": "node --test test/check-built-site.test.mjs test/validate-ecosystem.test.mjs test/validate-sources.test.mjs test/reference-manifest.test.mjs test/reference-update.test.mjs test/check-examples.test.mjs test/source-identity.test.mjs",
"test:built": "node --test test/reference-rendering.test.mjs test/api-coverage.test.mjs test/interface-reference-coverage.test.mjs test/site-chrome.test.mjs",
"typecheck": "tsc",
"update:references": "node scripts/reference/update.mjs",
Expand Down
82 changes: 44 additions & 38 deletions scripts/check-examples.mjs
Original file line number Diff line number Diff line change
@@ -1,52 +1,58 @@
import {access} from 'node:fs/promises';
import {access, readFile} from 'node:fs/promises';
import {spawnSync} from 'node:child_process';
import path from 'node:path';

const allowMissing = process.argv.includes('--allow-missing');
if (process.argv.slice(2).some((arg) => arg !== '--allow-missing')) {
throw new Error('usage: check-examples.mjs [--allow-missing]');
}
const versions = JSON.parse(
await readFile('data/reference/versions.json', 'utf8'),
);
const cppRoot = path.resolve(
process.env.NETFT_CPP_ROOT || '.dependencies/netft-cpp',
);
const python = process.env.PYTHON || 'python3';
function run(command, args) {
const result = spawnSync(command, args, {encoding: 'utf8'});
const result = spawnSync(command, args, {encoding: 'utf8', timeout: 60000});
if (result.status !== 0) {
process.stderr.write(result.stderr || result.stdout);
process.exit(result.status ?? 1);
throw new Error(
result.stderr || result.stdout || `${command} failed: ${result.error}`,
);
}
}

const cppRoot = path.resolve('..', 'netft-cpp');
let haveCpp = true;
try {
await access(path.join(cppRoot, 'include', 'netft', 'client.hpp'));
await access(path.join(cppRoot, 'include/netft/client.hpp'));
} catch {
console.log(
'Skipping C++ example: sibling netft-cpp checkout is unavailable.',
);
run(process.env.PYTHON || 'python3', [
'-m',
'py_compile',
'examples/python/read_sensor.py',
]);
process.exit(0);
haveCpp = false;
if (!allowMissing)
throw new Error(
'C++ SDK headers missing: set NETFT_CPP_ROOT or install the fixed .dependencies checkout',
);
console.log('Explicitly skipped C++ example: SDK headers unavailable.');
}

const compilerArgs = [
'-std=c++17',
'-fsyntax-only',
`-I${path.join(cppRoot, 'include')}`,
'examples/cpp/read_sensor.cpp',
];
const compiler = process.env.CXX || 'c++';
const probe = spawnSync(compiler, ['--version'], {encoding: 'utf8'});
if (probe.error?.code === 'ENOENT') {
run('pixi', [
'run',
'--manifest-path',
path.join(cppRoot, 'pixi.toml'),
compiler,
...compilerArgs,
if (haveCpp) {
run(process.env.CXX || 'c++', [
'-std=c++17',
'-fsyntax-only',
`-I${path.join(cppRoot, 'include')}`,
'examples/cpp/read_sensor.cpp',
]);
}
run(python, ['-m', 'py_compile', 'examples/python/read_sensor.py']);
const probe = spawnSync(python, ['-c', 'import pynetft'], {
encoding: 'utf8',
timeout: 60000,
});
if (probe.status !== 0) {
if (!allowMissing) throw new Error(probe.stderr || 'pynetft is unavailable');
console.log('Explicitly skipped Python import: pynetft unavailable.');
} else {
run(compiler, compilerArgs);
run(python, [
'-c',
'import importlib.metadata, runpy, sys; assert importlib.metadata.version("pynetft") == sys.argv[1], "pynetft version mismatch"; runpy.run_path("examples/python/read_sensor.py", run_name="__docs_check__")',
versions.components.pyNetFT.version,
]);
}

run(process.env.PYTHON || 'python3', [
'-m',
'py_compile',
'examples/python/read_sensor.py',
]);
Loading
Loading