Hertz is a React framework (or reconciler/renderer) for driving hardware peripherals. It projects the internal state of your React app to the physical world instead of a screen, video or terminal.
NOTE: This is still a work in progress. APIs may change and coverage is incomplete, but the project is usable for experimentation.
Hertz gives you React ergonomics for hardware control while keeping execution in a Node runtime.
- Declarative hardware tree: describe target hardware state as components and props.
- Built-in lifecycle mapping: mount/update/unmount map to peripheral init/apply/disown methods.
- Parent-first initialization: parent peripherals initialize before children.
- Poll-driven input updates: readable hardware values are queried and surfaced via
on...Changecallbacks. - Error propagation through React boundaries: hardware/runtime errors bubble to standard React error boundaries.
- External telemetry store: publish selected robot state for CLIs, dashboards, and monitoring.
Hertz is primarily a framework for building hardware reconcilers with React. This repository currently includes:
- the reconciler/runtime,
- telemetry utilities for publishing robot state outside React,
- a working ClearCore bridge and examples.
| Bridge | Status | Notes |
|---|---|---|
| ClearCore | Available | Active example bridge in src/bridges/clearcore |
| Arduino | Planned | Not implemented yet |
| Raspberry Pi | Planned | Not implemented yet |
See bridge-specific docs:
- ClearCore:
src/bridges/clearcore/README.md - Arduino (planned):
src/bridges/arduino/README.md - Raspberry Pi (planned):
src/bridges/raspberry/README.md - Bring your own hardware:
docs/bring-your-own-hardware.md - Testing:
docs/testing.md
Hertz runs in a Node.js-like runtime, not in a browser.
Node.jsis the primary target.Bun/Denomight work but are not officially tested yet.- Devices that cannot run Node directly (for example microcontrollers) are controlled from a host process over a transport such as serial.
- Install dependencies in your project:
pnpm add react github:zigapk/hertz
pnpm add serialport
pnpm add llamajet-driver-ts-
Flash ClearCore firmware from
src/bridges/clearcore/firmware/firmware.ino. -
Create the most basic program (blink one digital output pin):
import { ClearCore } from "llamajet-driver-ts";
import { useEffect, useState } from "react";
import { SerialPort } from "serialport";
import { CCDPinOut, clearCorePeripherals, createReconciler } from "hertz";
const Blink = () => {
const [value, setValue] = useState(false);
useEffect(() => {
const timer = setInterval(() => {
setValue((current) => !current);
}, 1000);
return () => clearInterval(timer);
}, []);
return <CCDPinOut pin={3} value={value} />;
};
async function main() {
const clearcore = new ClearCore(
new SerialPort({
path: "/dev/ttyACM0",
baudRate: 115200,
}),
);
await clearcore.connect();
const { render, runEventLoop } = createReconciler(
clearCorePeripherals,
clearcore,
);
render(<Blink />);
await runEventLoop();
}
void main();- Use one of the richer examples as a next step:
src/examples/clearcore-blink.tsxsrc/examples/clearcore-complex.tsxsrc/examples/clearcore-motor-position.tsxsrc/examples/clearcore-motor-velocity.tsxsrc/examples/clearcore-error-boundary.tsxsrc/examples/clearcore-blink-with-telemetry.tsx
The ClearCore bridge communicates over serial through llamajet-driver-ts, which expects the firmware protocol implemented by firmware.ino.
Hertz peripherals are wrapped by higher-level React components (createHigherLevelComponent) that propagate peripheral errors into the React tree:
- Peripheral lifecycle/read methods catch errors.
- They call an internal
onErrorcallback. - The wrapper stores the error in state and throws it on the next render.
- A React
ErrorBoundaryabove the peripheral catches it.
That means you can use standard React error boundaries to isolate failing robot subtrees and render safe fallbacks.
import { ErrorBoundary } from "react-error-boundary";
import { CCDPinIn, CCDPinOut } from "hertz";
const FaultyPeripheral = () => {
return (
<>
<CCDPinOut pin={0} value={true} />
<CCDPinIn
pin={1}
onValueChange={(value) => {
if (value) {
throw new Error("Pin 1 is HIGH");
}
}}
/>
</>
);
};
const Fallback = (_props: { resetErrorBoundary: () => void }) => {
return <CCDPinOut pin={2} value={true} />;
};
const Program = () => {
return (
<ErrorBoundary FallbackComponent={Fallback}>
<FaultyPeripheral />
</ErrorBoundary>
);
};For a complete runnable example, see src/examples/clearcore-error-boundary.tsx.
Telemetry lets you project selected React state into an external store so non-React code can observe it (CLI, dashboards, logs, safety monitors).
type Telemetry = {
blink: {
phase: "on" | "off";
toggles: number;
};
};
const telemetry = createTelemetryStore<Telemetry>({
blink: { phase: "off", toggles: 0 },
});
useTelemetry<Telemetry, ["blink"]>(["blink"], {
phase: value ? "on" : "off",
toggles,
});
telemetry.subscribeSelector(
(snapshot) => snapshot.blink.phase,
(next, previous) => console.log(`Phase changed: ${previous} -> ${next}`),
);See src/examples/clearcore-blink-with-telemetry.tsx for an end-to-end example.
This software controls physical hardware. Misconfiguration can damage equipment or cause injury. By using Hertz, you accept responsibility for safe setup, safe operation, and validation on your target hardware. The project maintainers are not liable for hardware damage, data loss, or personal injury resulting from use of this software.