Best use case
serial-monitor is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Teams using serial-monitor should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/serial-monitor/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How serial-monitor Compares
| Feature / Agent | serial-monitor | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
This skill provides specific capabilities for your AI agent. See the About section for full details.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Skill: serial-monitor
## What this skill knows
This skill covers the serial-monitor application: a pnpm-workspaces monorepo with a Node.js/Express backend and a React/Vite frontend. It covers the server-side serial port management, WebSocket broadcast layer, REST API routes, and the React store and hook architecture.
## Monorepo layout
```
serial-monitor/
packages/
server/src/
index.ts - Express + WS server entry point
ports.ts - SerialPort.list() wrapper
connection.ts - single active port management
websocket.ts - ws.Server + broadcast helper
parser.ts - parseNumeric from data lines
routes/
ports.ts - GET /api/ports
connect.ts - POST /api/connect, DELETE /api/connect
send.ts - POST /api/send
status.ts - GET /api/status
client/src/
components/
PortSelector/
SerialOutput/
LineChart/
SendBar/
ConnectionStatus/
pages/MonitorPage.tsx
store/useSerialStore.ts
hooks/useWebSocket.ts
```
## Environment variables
```
PORT=3001 # Express server port
WS_PORT=3002 # WebSocket server port
ALLOWED_ORIGIN=http://localhost:5173
```
Boolean env vars: `0` = false, `1` = true.
## API routes
| Method | Path | Body | Response |
|--------|------|------|----------|
| GET | `/api/ports` | - | `PortInfo[]` |
| POST | `/api/connect` | `{ port, baudRate }` | `200 OK` or `400` |
| DELETE | `/api/connect` | - | `200 OK` |
| POST | `/api/send` | `{ data }` | `200 OK` or `400` |
| GET | `/api/status` | - | `ConnectionInfo` |
## WebSocket message types
All messages are JSON with `{ type, payload }` shape.
```ts
type WsMessage =
| { type: 'data'; payload: { line: string; ts: number } }
| { type: 'status'; payload: ConnectionInfo }
| { type: 'ports'; payload: PortInfo[] }
| { type: 'error'; payload: { message: string } };
```
On new client connect: server immediately sends `status` and `ports` messages.
## connection.ts - key rules
- Only one active `SerialPort` instance at a time. Opening a new port closes the current one first.
- Use `ReadlineParser` with `delimiter: '\r\n'` to emit complete lines.
- `sendData` appends `\n` to the string before writing to the port.
- `broadcast` is imported from `websocket.ts` - it must be called from within `connection.ts` on data and error events.
```ts
// Correct open pattern
activePort = new SerialPort({ path, baudRate, autoOpen: false });
activeParser = activePort.pipe(new ReadlineParser({ delimiter: '\r\n' }));
activeParser.on('data', (line: string) => {
broadcast({ type: 'data', payload: { line, ts: Date.now() } });
});
await activePort.open();
```
## parser.ts
```ts
export function parseNumeric(line: string): number | null {
const match = line.match(/-?\d+(\.\d+)?/);
if (!match) return null;
const n = parseFloat(match[0]);
return isNaN(n) ? null : n;
}
```
This extracts the first integer or float from the line. It handles:
- `"42.5"` -> `42.5`
- `"temp: 24.5"` -> `24.5`
- `"x=99"` -> `99`
- `"SENSOR,24.5,1013"` -> `24.5`
- `"hello world"` -> `null`
## useSerialStore.ts (Zustand)
```ts
interface SerialStore {
connection: ConnectionInfo;
lines: DataLine[]; // capped at 500
chartValues: number[]; // capped at 200
ports: PortInfo[];
selectedPort: string;
selectedBaudRate: number;
setConnection(info: ConnectionInfo): void;
addLine(line: DataLine): void;
setChartValues(values: number[]): void;
setPorts(ports: PortInfo[]): void;
setSelectedPort(port: string): void;
setSelectedBaudRate(rate: number): void;
clearLines(): void;
}
```
Capping logic for `addLine`:
```ts
addLine: (line) => set((s) => {
const lines = [...s.lines, line].slice(-500);
const chartValues = line.numeric !== null
? [...s.chartValues, line.numeric].slice(-200)
: s.chartValues;
return { lines, chartValues };
}),
```
## useWebSocket.ts
- Connects to `ws://localhost:${WS_PORT}` on mount.
- On `onclose`: schedules reconnect after 2000ms.
- On unmount: calls `ws.close()` and clears the reconnect timer.
- Message handler dispatches to store:
- `data` -> `addLine` + numeric value appended to chart
- `status` -> `setConnection`
- `ports` -> `setPorts`
- `error` -> `addLine` with `direction: 'rx'` and a prefixed error text
```ts
function handleMessage(msg: WsMessage) {
const store = useSerialStore.getState();
if (msg.type === 'data') {
const { line, ts } = msg.payload;
const numeric = parseNumeric(line); // client-side parse for chart
store.addLine({ id: crypto.randomUUID(), timestamp: ts, text: line, numeric, direction: 'rx' });
} else if (msg.type === 'status') {
store.setConnection(msg.payload);
} else if (msg.type === 'ports') {
store.setPorts(msg.payload);
} else if (msg.type === 'error') {
store.addLine({ id: crypto.randomUUID(), timestamp: Date.now(), text: `ERROR: ${msg.payload.message}`, numeric: null, direction: 'rx' });
}
}
```
## SerialOutput auto-scroll
Track scroll state with a ref. Only auto-scroll if the user has not manually scrolled up.
```ts
const containerRef = useRef<HTMLDivElement>(null);
const userScrolled = useRef(false);
function onScroll() {
const el = containerRef.current;
if (!el) return;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
userScrolled.current = !atBottom;
}
useEffect(() => {
if (!userScrolled.current && containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [lines]);
```
Show a "Jump to bottom" button when `userScrolled.current === true`.
## LineChart configuration (Chart.js)
```ts
const options: ChartOptions<'line'> = {
animation: false, // required for real-time performance
responsive: true,
maintainAspectRatio: false,
scales: {
x: { display: false },
y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#6b7280', font: { size: 11 } } },
},
datasets: {
line: { pointRadius: 0, borderColor: '#d97706', borderWidth: 2,
backgroundColor: 'rgba(217,119,6,0.15)', fill: true },
},
};
```
Update data without full re-render:
```ts
useEffect(() => {
if (!chartRef.current) return;
chartRef.current.data.labels = chartValues.map((_, i) => i);
chartRef.current.data.datasets[0].data = chartValues;
chartRef.current.update('none'); // 'none' skips animation
}, [chartValues]);
```
## SendBar - POST /api/send
```ts
async function handleSend() {
if (!value.trim()) return;
const res = await fetch('/api/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: value }),
});
if (!res.ok) {
// show error toast
return;
}
// add sent line to store directly
store.addLine({ id: crypto.randomUUID(), timestamp: Date.now(), text: value, numeric: null, direction: 'tx' });
setValue('');
}
```
## Port polling
```ts
useEffect(() => {
async function poll() {
const res = await fetch('/api/ports');
if (res.ok) store.setPorts(await res.json());
}
poll();
const id = setInterval(poll, 5000);
return () => clearInterval(id);
}, []);
```
## Common baud rates
9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600. Default: 9600.
## Common mistakes
- Not calling `activePort.pipe(parser)` before `activePort.open()` - parser events will not fire.
- Calling `broadcast` before the WebSocket server is initialized. Initialize `ws.Server` in `index.ts` and export `broadcast` before the server starts.
- Storing `chartValues` as mutable array in Zustand and mutating it in place. Always use `.slice(-200)` to return a new array.
- Using `ws.send` inside the `connection` event before checking `ws.readyState === WebSocket.OPEN`.
- Vite proxy: in `vite.config.ts`, proxy `/api` to `http://localhost:3001` and `/ws` or add `ws: true` to the proxy config for the WebSocket.Related Skills
Skill: Uptime Monitoring
## Overview
serialport
No description provided.
ssl-cert-monitor
Operate ssl-cert-monitor -- add hosts, configure alert rules, trigger checks, review history, and deploy the stack.
backup-monitor
Track backup jobs via heartbeat pings, alert on missed or failed backups. Use when you need to monitor scheduled backup scripts, get alerted when a backup misses its window, or track backup execution history. Triggers include "backup monitoring", "backup alerts", "missed backup", "backup heartbeat", "backup job tracking", or any task involving backup reliability verification.
cron-monitor
Send heartbeat pings to cron-monitor after cron job completion, check job status, and register new jobs. Use when you need to confirm a scheduled task ran successfully, check if a cron job is healthy, or add monitoring to a new cron script. Triggers include "ping cron-monitor", "check job status", "register cron job", "heartbeat", "cron health check", or any task involving scheduled job monitoring.
database-size-monitor
Dashboard for monitoring PostgreSQL and MySQL table sizes over time, with growth tracking, threshold alerts, and snapshot comparison
data-pipeline-monitor
Track ETL and data pipeline jobs with success/failure status, duration tracking, heartbeat monitoring, and dependency visualization. Use when you need to monitor scheduled jobs, detect failures, track pipeline health over time, or visualize ETL step dependencies. Triggers include "pipeline monitoring", "job tracking", "ETL status", "cron job health", "heartbeat monitor", "pipeline failed", or any task involving monitoring data workflows.
process-monitor
Monitor system processes for resource usage using process-tree watch mode. Use when tracking CPU or memory usage over time, finding resource hogs, or watching a specific process. Triggers include "monitor processes", "watch cpu usage", "process monitor", "top processes", "resource usage", "ptree watch".
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.