mirror of
https://github.com/Vale54321/schafkop-neu.git
synced 2025-12-13 10:39:33 +01:00
feat: integrate Tailwind CSS for improved styling and layout
- Added Tailwind CSS and its Vite plugin to the frontend project. - Updated App.svelte to enhance UI with Tailwind classes, including a new layout for the serial monitor and command tester. - Improved logging functionality by increasing log history and adding more detailed log messages. - Refactored SendBox.svelte for consistent styling with Tailwind. - Enhanced Pico firmware to support structured event logging and command parsing. - Updated README_PICO_SERIAL.md to provide comprehensive documentation on serial communication and backend integration. - Added .dockerignore to optimize Docker builds by excluding unnecessary files.
This commit is contained in:
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
dist
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
Dockerfile*
|
||||||
|
frontend/dist
|
||||||
|
backend/coverage
|
||||||
@@ -1,8 +1,29 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
bufferLogs: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Serve static frontend (built assets copied to /app/public in container)
|
||||||
|
const publicDir = join(process.cwd(), 'public');
|
||||||
|
app.useStaticAssets(publicDir);
|
||||||
|
|
||||||
|
// Basic health endpoint
|
||||||
|
app.getHttpAdapter().get('/healthz', (_req: any, res: any) => {
|
||||||
|
res.json({ ok: true, ts: Date.now() });
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = Number(process.env.PORT || 3000);
|
||||||
|
await app.listen(port);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`[bootstrap] Listening on :${port}`);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap().catch((e) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Fatal bootstrap error', e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
@@ -139,16 +139,44 @@ export class SerialService implements OnModuleDestroy, OnModuleInit {
|
|||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onModuleInit() {
|
async onModuleInit() {
|
||||||
const port = process.env.SERIAL_PORT;
|
// Priority: explicit env vars > auto-detect
|
||||||
const baud = process.env.SERIAL_BAUD ? Number(process.env.SERIAL_BAUD) : undefined;
|
const explicit = process.env.SERIAL_PORT || process.env.PICO_SERIAL_PORT;
|
||||||
if (port) {
|
const baud = Number(process.env.SERIAL_BAUD || process.env.PICO_BAUD || 115200);
|
||||||
|
if (explicit) {
|
||||||
try {
|
try {
|
||||||
console.log('[SerialService] AUTO opening port from SERIAL_PORT=', port, 'baud=', baud ?? 115200);
|
console.log('[SerialService] AUTO opening explicit port', explicit, 'baud=', baud);
|
||||||
this.open(port, baud ?? 115200);
|
this.open(explicit, baud);
|
||||||
|
return;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.warn('[SerialService] AUTO open failed', e?.message ?? e);
|
console.warn('[SerialService] explicit open failed', e?.message ?? e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt auto-detect of Pico (USB first, then common UART paths)
|
||||||
|
try {
|
||||||
|
const ports = await this.listPorts();
|
||||||
|
// Prefer vendorId 2e8a (Pico)
|
||||||
|
let candidate = ports.find(p => (p.vendorId || '').toLowerCase() === '2e8a');
|
||||||
|
if (!candidate) {
|
||||||
|
const common = ['/dev/serial0', '/dev/ttyACM0', '/dev/ttyAMA0'];
|
||||||
|
candidate = ports.find(p => p.path && common.includes(p.path));
|
||||||
|
}
|
||||||
|
// Windows fallback: highest numbered COM port
|
||||||
|
if (!candidate) {
|
||||||
|
const winCom = ports
|
||||||
|
.filter(p => /^COM\d+$/i.test(p.path))
|
||||||
|
.sort((a, b) => Number(b.path.replace(/\D/g, '')) - Number(a.path.replace(/\D/g, '')))[0];
|
||||||
|
if (winCom) candidate = winCom;
|
||||||
|
}
|
||||||
|
if (candidate && candidate.path) {
|
||||||
|
console.log('[SerialService] AUTO opening detected port', candidate.path);
|
||||||
|
this.open(candidate.path, baud);
|
||||||
|
} else {
|
||||||
|
console.log('[SerialService] No serial port auto-detected (will remain idle)');
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.warn('[SerialService] auto-detect failed', e?.message ?? e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,48 @@
|
|||||||
# Multi-stage build for SvelteKit (static client + node server fallback)
|
######## Combined Frontend + Backend build (single Node runtime) ########
|
||||||
FROM node:20-bookworm-slim AS builder
|
# This Dockerfile now builds BOTH the Svelte frontend and the NestJS backend
|
||||||
WORKDIR /app
|
# and serves the compiled frontend through the backend (Express static).
|
||||||
|
|
||||||
# Install deps (use package-lock if present)
|
########################
|
||||||
COPY package*.json ./
|
# 1) Frontend build #
|
||||||
|
########################
|
||||||
|
FROM node:20-bookworm-slim AS frontend-build
|
||||||
|
WORKDIR /frontend
|
||||||
|
COPY frontend/package*.json ./
|
||||||
RUN npm install --no-audit --no-fund
|
RUN npm install --no-audit --no-fund
|
||||||
|
COPY frontend/ .
|
||||||
# Copy sources and build
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Use a lightweight nginx to serve the client build output
|
########################
|
||||||
FROM nginx:alpine AS runner
|
# 2) Backend build #
|
||||||
COPY --from=builder /app/dist /usr/share/nginx/html
|
########################
|
||||||
|
FROM node:20-bookworm-slim AS backend-build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm install --no-audit --no-fund
|
||||||
|
COPY backend/ .
|
||||||
|
# Copy compiled frontend into backend/public (served statically by Nest)
|
||||||
|
COPY --from=frontend-build /frontend/dist ./public
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
# Default nginx port
|
########################
|
||||||
EXPOSE 80
|
# 3) Production image #
|
||||||
|
########################
|
||||||
|
FROM node:20-bookworm-slim AS runner
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
# Install only production deps (reuse original package.json)
|
||||||
|
COPY backend/package*.json ./
|
||||||
|
RUN npm install --omit=dev --no-audit --no-fund
|
||||||
|
|
||||||
|
# Copy backend dist + public assets
|
||||||
|
COPY --from=backend-build /app/dist ./dist
|
||||||
|
COPY --from=backend-build /app/public ./public
|
||||||
|
|
||||||
|
# Environment (override at runtime as needed)
|
||||||
|
ENV PORT=3000 \
|
||||||
|
SERIAL_BAUD=115200
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
services:
|
services:
|
||||||
frontend:
|
app:
|
||||||
build: .
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: frontend/Dockerfile
|
||||||
|
container_name: schafkop-app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
|
- PORT=3000
|
||||||
|
# Optionally set SERIAL_PORT or PICO_SERIAL_PORT for auto-open
|
||||||
|
# - SERIAL_PORT=/dev/serial0
|
||||||
|
# - SERIAL_BAUD=115200
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:3000"
|
||||||
|
# Uncomment and adjust one of the following for hardware access on a Pi:
|
||||||
|
# devices:
|
||||||
|
# - /dev/serial0:/dev/serial0
|
||||||
|
# - /dev/ttyACM0:/dev/ttyACM0
|
||||||
|
|||||||
697
frontend/package-lock.json
generated
697
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,11 @@
|
|||||||
"@tsconfig/svelte": "^5.0.4",
|
"@tsconfig/svelte": "^5.0.4",
|
||||||
"svelte": "^5.38.1",
|
"svelte": "^5.38.1",
|
||||||
"svelte-check": "^4.3.1",
|
"svelte-check": "^4.3.1",
|
||||||
|
"tailwindcss": "^4.1.12",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"vite": "^7.1.2"
|
"vite": "^7.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.1.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
let es: EventSource | null = null;
|
let es: EventSource | null = null;
|
||||||
|
|
||||||
function addLog(line: string) {
|
function addLog(line: string) {
|
||||||
log.update((l) => [new Date().toISOString() + ' ' + line, ...l].slice(0, 200));
|
// keep a fairly large history and prefix with an ISO timestamp
|
||||||
|
log.update((l) => [new Date().toISOString() + ' ' + line, ...l].slice(0, 2000));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function list() {
|
async function list() {
|
||||||
@@ -26,14 +27,18 @@
|
|||||||
const res = await fetch('/serial/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path }) });
|
const res = await fetch('/serial/open', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path }) });
|
||||||
const j = await res.json().catch(() => null);
|
const j = await res.json().catch(() => null);
|
||||||
console.log('[serial] openPort result', j);
|
console.log('[serial] openPort result', j);
|
||||||
// refresh status
|
// log result and refresh status
|
||||||
|
if (j?.ok) addLog('PORT: open requested ' + path);
|
||||||
|
else addLog('PORT: open request failed - ' + (j?.error ?? 'unknown'));
|
||||||
setTimeout(getStatus, 300);
|
setTimeout(getStatus, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closePort() {
|
async function closePort() {
|
||||||
console.log('[serial] closePort()');
|
console.log('[serial] closePort()');
|
||||||
const res = await fetch('/serial/close', { method: 'POST' });
|
const res = await fetch('/serial/close', { method: 'POST' });
|
||||||
console.log('[serial] closePort result', await res.json().catch(() => null));
|
const j = await res.json().catch(() => null);
|
||||||
|
console.log('[serial] closePort result', j);
|
||||||
|
addLog('PORT: close requested');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendLine(txt: string) {
|
async function sendLine(txt: string) {
|
||||||
@@ -61,13 +66,17 @@
|
|||||||
if (es) return;
|
if (es) return;
|
||||||
console.log('[serial] startStream()');
|
console.log('[serial] startStream()');
|
||||||
es = new EventSource('/serial/stream');
|
es = new EventSource('/serial/stream');
|
||||||
es.addEventListener('open', () => { console.log('[serial][SSE] open'); connected.set(true); });
|
// connection lifecycle events - also log them so the serial box shows open/close/errors
|
||||||
es.addEventListener('close', () => { console.log('[serial][SSE] close'); connected.set(false); });
|
es.addEventListener('open', () => { console.log('[serial][SSE] open'); connected.set(true); addLog('[SSE] connected'); });
|
||||||
es.addEventListener('error', (e: any) => { console.warn('[serial][SSE] error', e); addLog('SSE error'); });
|
es.addEventListener('close', () => { console.log('[serial][SSE] close'); connected.set(false); addLog('[SSE] disconnected'); });
|
||||||
|
es.addEventListener('error', (e: any) => { console.warn('[serial][SSE] error', e); addLog('[SSE] error: ' + (e?.message ?? JSON.stringify(e))); });
|
||||||
|
|
||||||
|
// data events contain the actual lines emitted by the Pico; parse JSON payloads but fall back to raw
|
||||||
es.addEventListener('data', (ev: any) => {
|
es.addEventListener('data', (ev: any) => {
|
||||||
console.log('[serial][SSE] data event', ev.data);
|
console.log('[serial][SSE] data event', ev.data);
|
||||||
try {
|
try {
|
||||||
const d = JSON.parse(ev.data);
|
const d = JSON.parse(ev.data);
|
||||||
|
// always show the raw line emitted by the firmware
|
||||||
addLog('RX: ' + d.line);
|
addLog('RX: ' + d.line);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
addLog('RX (raw): ' + ev.data);
|
addLog('RX (raw): ' + ev.data);
|
||||||
@@ -83,6 +92,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let statusInterval: any;
|
let statusInterval: any;
|
||||||
|
// quick test command defaults
|
||||||
|
let stepVal: string = '4096';
|
||||||
|
let dirVal: string = '1';
|
||||||
|
let speedVal: string = '3000';
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
list();
|
list();
|
||||||
startStream();
|
startStream();
|
||||||
@@ -93,43 +106,109 @@
|
|||||||
stopStream();
|
stopStream();
|
||||||
clearInterval(statusInterval);
|
clearInterval(statusInterval);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function sendStepCmd() {
|
||||||
|
const steps = Number(stepVal);
|
||||||
|
const dir = Number(dirVal) === 1 ? 1 : 0;
|
||||||
|
if (!Number.isFinite(steps) || steps <= 0) {
|
||||||
|
addLog('TX error: invalid STEP value: ' + stepVal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const line = `STEP ${steps} ${dir}`;
|
||||||
|
sendLine(line);
|
||||||
|
addLog('TX: ' + line);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendSpeedCmd() {
|
||||||
|
const d = Number(speedVal);
|
||||||
|
if (!Number.isFinite(d) || d <= 0) {
|
||||||
|
addLog('TX error: invalid SPEED value: ' + speedVal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const line = `SPEED ${d}`;
|
||||||
|
sendLine(line);
|
||||||
|
addLog('TX: ' + line);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main class="min-h-screen bg-gradient-to-b from-bg to-surface text-gray-100 p-8">
|
||||||
<h1>Raspberry Pico — Serial Monitor</h1>
|
<h1 class="text-3xl font-bold underline">
|
||||||
|
Hello world!
|
||||||
|
</h1>
|
||||||
|
<div class="container mx-auto">
|
||||||
|
<header class="flex items-center justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl font-semibold">Raspberry Pico</h1>
|
||||||
|
<p class="text-sm text-gray-400">Serial monitor & command tester</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-sm text-gray-300">SSE: {$connected ? 'connected' : 'disconnected'}</span>
|
||||||
|
<button class="px-3 py-1 bg-primary text-white rounded-md text-sm" on:click={startStream}>Start</button>
|
||||||
|
<button class="px-3 py-1 bg-gray-700 text-white rounded-md text-sm" on:click={stopStream}>Stop</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<section>
|
<div class="grid grid-cols-12 gap-6">
|
||||||
<h3>Available ports</h3>
|
<aside class="col-span-4 bg-gray-900/60 p-4 rounded-lg border border-gray-800">
|
||||||
<button on:click={list}>Refresh</button>
|
<section class="mb-4">
|
||||||
<ul>
|
<h3 class="text-sm text-gray-300 font-medium">Available ports</h3>
|
||||||
|
<div class="flex items-center gap-2 mt-2">
|
||||||
|
<button class="px-2 py-1 text-sm bg-gray-700 rounded" on:click={list}>Refresh</button>
|
||||||
|
<button class="px-2 py-1 text-sm bg-red-600 rounded" on:click={closePort}>Close port</button>
|
||||||
|
</div>
|
||||||
|
<ul class="mt-3 space-y-2">
|
||||||
{#each $ports as p}
|
{#each $ports as p}
|
||||||
<li>
|
<li class="flex items-center justify-between bg-gray-800/40 p-2 rounded">
|
||||||
<code>{p.path}</code>
|
<code class="text-xs text-gray-200">{p.path}</code>
|
||||||
<button on:click={() => openPort(p.path)}>Open</button>
|
<button class="px-2 py-1 text-sm bg-primary rounded" on:click={() => openPort(p.path)}>Open</button>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<button on:click={closePort}>Close port</button>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section class="mb-4">
|
||||||
<h3>Send</h3>
|
<h3 class="text-sm text-gray-300 font-medium">Send</h3>
|
||||||
<div style="display:flex; align-items:center; gap:1rem;">
|
<div class="mt-2 flex items-center gap-2">
|
||||||
<div>Port: {$portOpen ? 'open' : 'closed'}</div>
|
<div class="text-xs text-gray-300">Port: <span class="font-medium">{$portOpen ? 'open' : 'closed'}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
<SendBox disabled={!$portOpen} on:send={(e) => { sendLine(e.detail); addLog('TX: ' + e.detail); }} />
|
<SendBox disabled={!$portOpen} on:send={(e) => { sendLine(e.detail); addLog('TX: ' + e.detail); }} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3>Live log ({$connected ? 'connected' : 'disconnected'})</h3>
|
<h3 class="text-sm text-gray-300 font-medium">Quick commands</h3>
|
||||||
<button on:click={startStream}>Start stream</button>
|
<div class="mt-2 space-y-2">
|
||||||
<button on:click={stopStream}>Stop stream</button>
|
<div class="flex gap-2 items-center">
|
||||||
<div style="height:320px; overflow:auto; background:#111; color:#dfe6ef; padding:8px; border-radius:6px;">
|
<input class="w-28 p-2 rounded bg-gray-800 text-sm" type="number" bind:value={stepVal} min="1" />
|
||||||
|
<select class="p-2 rounded bg-gray-800 text-sm" bind:value={dirVal}>
|
||||||
|
<option value="1">1 (forward)</option>
|
||||||
|
<option value="0">0 (reverse)</option>
|
||||||
|
</select>
|
||||||
|
<button class="px-3 py-1 bg-primary text-white rounded" on:click={sendStepCmd} disabled={!$portOpen}>Send STEP</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 items-center">
|
||||||
|
<input class="w-28 p-2 rounded bg-gray-800 text-sm" type="number" bind:value={speedVal} min="1" />
|
||||||
|
<button class="px-3 py-1 bg-primary text-white rounded" on:click={sendSpeedCmd} disabled={!$portOpen}>Send SPEED</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="col-span-8 bg-gray-900/60 p-4 rounded-lg border border-gray-800 flex flex-col">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<h3 class="text-sm text-gray-300 font-medium">Live log</h3>
|
||||||
|
<div class="text-xs text-gray-400">Showing last {2000} lines</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-auto bg-[#0b0b0b] p-3 rounded text-sm font-mono text-gray-100">
|
||||||
{#each $log as l}
|
{#each $log as l}
|
||||||
<div><code>{l}</code></div>
|
<div class="py-0.5"><code class="text-xs">{l}</code></div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div style="margin-top:0.5rem; display:flex; gap:0.5rem; align-items:center;">
|
<div class="mt-2 flex gap-2 items-center">
|
||||||
<input bind:value={txt} placeholder={placeholder} on:keydown={(e) => e.key === 'Enter' && doSend()} style="flex:1; padding:0.4rem;" disabled={disabled} />
|
<input class="flex-1 p-2 rounded bg-gray-800 text-sm text-gray-100" bind:value={txt} placeholder={placeholder} on:keydown={(e) => e.key === 'Enter' && doSend()} disabled={disabled} />
|
||||||
<button on:click={doSend} disabled={disabled}>Send</button>
|
<button class="px-3 py-1 bg-primary text-white rounded text-sm" on:click={doSend} disabled={disabled}>Send</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,3 +30,15 @@ a:hover { opacity: 0.9; }
|
|||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root { --bg: #fafafa; --surface:#fff; --text:#111827; --muted:#556; }
|
:root { --bg: #fafafa; --surface:#fff; --text:#111827; --muted:#556; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* small helpers that complement tailwind tokens */
|
||||||
|
.container { max-width: 1100px; margin: 0 auto; padding: 1.25rem; }
|
||||||
|
|
||||||
|
:focus { outline: 3px solid rgba(124,108,255,0.18); outline-offset: 2px; }
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
@@ -1,9 +1,13 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [svelte()],
|
plugins: [
|
||||||
|
svelte(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
// forward serial API calls (including SSE) to backend running on :3000
|
// forward serial API calls (including SSE) to backend running on :3000
|
||||||
|
|||||||
@@ -1,35 +1,280 @@
|
|||||||
# Pico Serial Motor Control Integration
|
# Pico Serial Motor Control — README for Copilot/Developers
|
||||||
|
|
||||||
This project enables a Raspberry Pi Pico to be controlled via serial commands from a TypeScript backend. The Pico firmware parses commands received over serial and controls a stepper motor accordingly, while also sending log/status messages back.
|
Purpose
|
||||||
|
-------
|
||||||
|
This file documents the exact serial message contract between the Raspberry Pi Pico firmware and a TypeScript backend. It's written so another Copilot or developer can implement a backend and extend firmware features without guesswork.
|
||||||
|
|
||||||
## Serial Commands Supported
|
Transport modes
|
||||||
- `STEP <steps> <direction>`: Move the motor by the specified number of steps. Direction: 1 for forward, 0 for reverse.
|
---------------
|
||||||
- `SPEED <delay_us>`: Set the motor speed (delay in microseconds between steps).
|
The firmware supports two serial transports (selected automatically):
|
||||||
- `LOG <message>`: Print a log message to serial.
|
|
||||||
|
|
||||||
## Firmware Files
|
1. USB CDC (preferred)
|
||||||
- `src/main.cpp`: Main firmware logic, serial command parsing, motor control, logging.
|
- When the Pico is connected over USB and a host opens the port, firmware uses `Serial`.
|
||||||
- `src/ULN2003Stepper.h`, `src/Stepper.h`: Stepper motor driver classes.
|
- Typical device name:
|
||||||
|
- Windows: `COMx`
|
||||||
|
- Linux (Pi): `/dev/ttyACM0`
|
||||||
|
- Vendor/Product IDs: 0x2E8A / 0x000A (can be used for auto-detection).
|
||||||
|
|
||||||
## How to Extend
|
2. UART0 on GPIO0/GP0 (TX) and GPIO1/GP1 (RX)
|
||||||
- Add new serial commands by updating the parser in `main.cpp`.
|
- Activated if USB CDC is not opened within ~2 seconds at boot; firmware falls back to `Serial1`.
|
||||||
- Implement additional motor features or logging as needed.
|
- Used for headless integration when the Pico is cabled to a Raspberry Pi's UART pins.
|
||||||
- Ensure any new global variables are only defined in one source file, and declared as `extern` elsewhere.
|
|
||||||
|
|
||||||
## TypeScript Backend
|
Wiring (Pico UART0 <-> Raspberry Pi UART)
|
||||||
- Use a library like `serialport` to communicate with the Pico.
|
----------------------------------------
|
||||||
- Send commands as plain text terminated by `\n`.
|
All signals are 3.3V. DO NOT connect 5V to Pico GPIOs.
|
||||||
- Read and process log/status messages from serial.
|
|
||||||
|
| Function | Pico Pin | Pi (BCM) | Pi Physical Pin |
|
||||||
|
|----------|----------|----------|-----------------|
|
||||||
|
| UART0 TX | GP0 | RXD0 (15)| 10 |
|
||||||
|
| UART0 RX | GP1 | TXD0 (14)| 8 |
|
||||||
|
| GND | GND | GND | (any GND) |
|
||||||
|
|
||||||
|
Power options:
|
||||||
|
- Preferred: Power Pico over USB (isolated data + power). Only connect GND and TX/RX for UART logic level link.
|
||||||
|
- Alternate: Power from Pi 3V3 pin to Pico 3V3 pin (NOT VBUS) plus GND. Never power from USB and Pi 3V3 simultaneously unless you know backfeed protection is in place.
|
||||||
|
|
||||||
|
Raspberry Pi configuration (enable UART)
|
||||||
|
---------------------------------------
|
||||||
|
Edit `/boot/firmware/config.txt` (newer Raspberry Pi OS) or `/boot/config.txt` (older) and ensure:
|
||||||
|
|
||||||
## Example Serial Session
|
|
||||||
```
|
```
|
||||||
SPEED 3000
|
enable_uart=1
|
||||||
|
```
|
||||||
|
|
||||||
|
If the serial console/login is enabled, you may need to disable it (via `sudo raspi-config` -> Interface Options -> Serial) so `/dev/serial0` is free.
|
||||||
|
|
||||||
|
After reboot you should see one of:
|
||||||
|
- `/dev/serial0` (symlink to the primary UART)
|
||||||
|
- `/dev/ttyAMA0` or `/dev/ttyS0` depending on Pi model.
|
||||||
|
|
||||||
|
Docker usage on Raspberry Pi
|
||||||
|
----------------------------
|
||||||
|
Expose the UART device inside the container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run \
|
||||||
|
--device /dev/serial0:/dev/serial0 \
|
||||||
|
-e PICO_SERIAL_PORT=/dev/serial0 \
|
||||||
|
your-image:tag
|
||||||
|
```
|
||||||
|
|
||||||
|
If using USB instead of GPIO UART, expose `/dev/ttyACM0` (or appropriate) similarly.
|
||||||
|
|
||||||
|
Single combined app container (frontend + backend)
|
||||||
|
-------------------------------------------------
|
||||||
|
The project Dockerfile now builds both the frontend and backend. To run on a Pi with GPIO UART wiring:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t schafkop-app .
|
||||||
|
docker run --rm \
|
||||||
|
--device /dev/serial0:/dev/serial0 \
|
||||||
|
-e PICO_SERIAL_PORT=/dev/serial0 \
|
||||||
|
-p 80:3000 \
|
||||||
|
schafkop-app
|
||||||
|
```
|
||||||
|
|
||||||
|
For USB Pico:
|
||||||
|
```
|
||||||
|
docker run --rm \
|
||||||
|
--device /dev/ttyACM0:/dev/ttyACM0 \
|
||||||
|
-e PICO_SERIAL_PORT=/dev/ttyACM0 \
|
||||||
|
-p 80:3000 \
|
||||||
|
schafkop-app
|
||||||
|
```
|
||||||
|
|
||||||
|
If you omit PICO_SERIAL_PORT the backend will attempt auto-detection (vendorId 2e8a or common paths).
|
||||||
|
|
||||||
|
Environment variables (suggested backend behavior):
|
||||||
|
- `PICO_SERIAL_PORT`: If set, backend uses this path directly.
|
||||||
|
- `PICO_BAUD`: Override baud rate (default 115200).
|
||||||
|
|
||||||
|
Backend port auto-detection logic (recommended order):
|
||||||
|
1. If `PICO_SERIAL_PORT` env var exists, use it.
|
||||||
|
2. Else list serial ports; prefer any with vendorId `2e8a` (Pico USB).
|
||||||
|
3. Else probe common paths: `/dev/serial0`, `/dev/ttyACM0`, `/dev/ttyAMA0`, Windows `COM` ports (highest matching newly added), macOS `/dev/tty.usbmodem*`.
|
||||||
|
4. Fallback: error with clear message.
|
||||||
|
|
||||||
|
Contract (high-level)
|
||||||
|
---------------------
|
||||||
|
- Commands to Pico: ASCII text lines terminated by LF ("\\n") or CRLF ("\\r\\n").
|
||||||
|
- Events/logs from Pico: ASCII text lines, one event per line. Backend must split on newlines.
|
||||||
|
- Baud rate: 115200 (firmware uses Serial.begin(115200)).
|
||||||
|
|
||||||
|
Accepted commands (input to Pico)
|
||||||
|
---------------------------------
|
||||||
|
1) STEP <steps> <direction>
|
||||||
|
- steps: integer (positive number of micro-steps)
|
||||||
|
- direction: 1 (forward) or 0 (reverse)
|
||||||
|
- Example: `STEP 4096 1`
|
||||||
|
|
||||||
|
2) SPEED <delay_us>
|
||||||
|
- delay_us: integer microseconds between internal step micro-operations
|
||||||
|
- Example: `SPEED 3000`
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Commands are trimmed for leading/trailing whitespace before parsing.
|
||||||
|
- Invalid or malformed commands result in an error event emitted by the Pico.
|
||||||
|
|
||||||
|
Events/log lines emitted by Pico (output)
|
||||||
|
----------------------------------------
|
||||||
|
The firmware emits structured lines with prefixes so the backend can parse them easily.
|
||||||
|
|
||||||
|
- `EVENT:RECEIVED <raw-command>`
|
||||||
|
- Emitted immediately when a command line is received (before execution).
|
||||||
|
- Example: `EVENT:RECEIVED STEP 4096 1`
|
||||||
|
|
||||||
|
- `EVENT:COMPLETED STEP <steps> <direction>`
|
||||||
|
- Emitted after a successful STEP command completes.
|
||||||
|
- Example: `EVENT:COMPLETED STEP 4096 1`
|
||||||
|
|
||||||
|
- `EVENT:COMPLETED SPEED <delay_us>`
|
||||||
|
- Emitted after the SPEED command is applied.
|
||||||
|
|
||||||
|
- `EVENT:COMPLETED ERROR: <message>`
|
||||||
|
- Emitted when a command fails to parse or run.
|
||||||
|
- Example: `EVENT:COMPLETED ERROR: malformed STEP command`
|
||||||
|
|
||||||
|
- `STATUS: OK` or `STATUS: ERROR: <message>`
|
||||||
|
- Short summary always emitted after processing a command.
|
||||||
|
|
||||||
|
Parsing guidance for backend:
|
||||||
|
- Read raw bytes, split on `\\n` (handle `\\r\\n`).
|
||||||
|
- Ignore empty lines.
|
||||||
|
- Inspect prefixes `EVENT:RECEIVED`, `EVENT:COMPLETED`, `STATUS:` and parse the remainder.
|
||||||
|
|
||||||
|
Backend responsibilities (TypeScript)
|
||||||
|
------------------------------------
|
||||||
|
- Open the serial port at 115200 baud.
|
||||||
|
- Provide a small API:
|
||||||
|
- `sendStep(steps: number, direction: 0|1): Promise<void>`
|
||||||
|
- `setSpeed(delayUs: number): Promise<void>`
|
||||||
|
- `onEvent(cb: (evt: {type: string; payload: string}) => void)`
|
||||||
|
- Send commands as ASCII lines terminated by `\\n`.
|
||||||
|
- Listen for events; map them to higher-level promises if desired.
|
||||||
|
- Implement reconnect logic with exponential backoff if the device disconnects.
|
||||||
|
- Add basic validation before sending commands to avoid malformed requests.
|
||||||
|
|
||||||
|
Suggested libraries / implementation notes
|
||||||
|
-----------------------------------------
|
||||||
|
- Use `serialport` (npm) and its `ReadlineParser` or equivalent.
|
||||||
|
- Keep a small FIFO of pending commands if you want to wait for `EVENT:COMPLETED` per command.
|
||||||
|
- For each command you can:
|
||||||
|
1. Write the line `CMD\\n` to the port.
|
||||||
|
2. Wait for `EVENT:RECEIVED CMD` then `EVENT:COMPLETED ...` and `STATUS: ...`.
|
||||||
|
- Ensure the backend tolerates duplicate or out-of-order messages (don't assume perfect timing).
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
Example sequence for a STEP command:
|
||||||
|
|
||||||
|
```
|
||||||
|
// backend writes:
|
||||||
STEP 4096 1
|
STEP 4096 1
|
||||||
LOG Motor moved
|
|
||||||
|
// pico emits:
|
||||||
|
EVENT:RECEIVED STEP 4096 1
|
||||||
|
EVENT:COMPLETED STEP 4096 1
|
||||||
|
STATUS: OK
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build & Upload
|
TypeScript example (auto-detect & simple API)
|
||||||
Use PlatformIO to build and upload the firmware to the Pico.
|
--------------------------------------------
|
||||||
|
```ts
|
||||||
|
import { SerialPort } from 'serialport';
|
||||||
|
import { ReadlineParser } from '@serialport/parser-readline';
|
||||||
|
|
||||||
|
interface PicoEvent { type: string; payload: string; raw: string; }
|
||||||
|
|
||||||
|
async function findPort(): Promise<string> {
|
||||||
|
if (process.env.PICO_SERIAL_PORT) return process.env.PICO_SERIAL_PORT;
|
||||||
|
const ports = await SerialPort.list();
|
||||||
|
// Prefer Pico USB (vendorId 2e8a)
|
||||||
|
const pico = ports.find(p => (p.vendorId||'').toLowerCase()==='2e8a');
|
||||||
|
if (pico && pico.path) return pico.path;
|
||||||
|
const candidates = ['/dev/serial0','/dev/ttyACM0','/dev/ttyAMA0'];
|
||||||
|
for (const c of candidates) if (ports.find(p=>p.path===c)) return c;
|
||||||
|
if (ports[0]) return ports[0].path; // fallback
|
||||||
|
throw new Error('No serial ports detected for Pico');
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PicoClient {
|
||||||
|
private port!: SerialPort;
|
||||||
|
private parser!: ReadlineParser;
|
||||||
|
private listeners: ((e:PicoEvent)=>void)[] = [];
|
||||||
|
constructor(private baud = Number(process.env.PICO_BAUD)||115200) {}
|
||||||
|
async init() {
|
||||||
|
const path = await findPort();
|
||||||
|
this.port = new SerialPort({ path, baudRate: this.baud });
|
||||||
|
this.parser = this.port.pipe(new ReadlineParser({ delimiter: '\n' }));
|
||||||
|
this.parser.on('data', line => {
|
||||||
|
const l = line.trim();
|
||||||
|
if (!l) return;
|
||||||
|
let type='RAW'; let payload=l;
|
||||||
|
if (l.startsWith('EVENT:RECEIVED ')) { type='EVENT:RECEIVED'; payload=l.substring(15); }
|
||||||
|
else if (l.startsWith('EVENT:COMPLETED ')) { type='EVENT:COMPLETED'; payload=l.substring(17); }
|
||||||
|
else if (l.startsWith('STATUS: ')) { type='STATUS'; payload=l.substring(8); }
|
||||||
|
this.listeners.forEach(cb=>cb({ type, payload, raw:l }));
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
onEvent(cb:(e:PicoEvent)=>void){ this.listeners.push(cb); }
|
||||||
|
private write(cmd:string){ this.port.write(cmd.endsWith('\n')?cmd:cmd+'\n'); }
|
||||||
|
sendStep(steps:number, dir:0|1){ this.write(`STEP ${steps} ${dir}`); }
|
||||||
|
setSpeed(delayUs:number){ this.write(`SPEED ${delayUs}`); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage example
|
||||||
|
// (async () => {
|
||||||
|
// const pico = await new PicoClient().init();
|
||||||
|
// pico.onEvent(e => console.log('EVENT', e));
|
||||||
|
// pico.setSpeed(3000);
|
||||||
|
// pico.sendStep(4096,1);
|
||||||
|
// })();
|
||||||
|
```
|
||||||
|
|
||||||
|
Manual testing
|
||||||
|
--------------
|
||||||
|
- Build and upload firmware with PlatformIO (pico environment).
|
||||||
|
- Open a serial terminal at 115200 and send commands.
|
||||||
|
- Observe `EVENT:` and `STATUS:` lines.
|
||||||
|
|
||||||
|
Testing inside Docker (Pi UART example)
|
||||||
|
--------------------------------------
|
||||||
|
1. Connect wiring (see table above) and power Pico.
|
||||||
|
2. Confirm `/dev/serial0` exists on host (`ls -l /dev/serial0`).
|
||||||
|
3. Run container with device passed through.
|
||||||
|
4. Inside container: run a small Node script using the TypeScript example (compiled) or `screen /dev/serial0 115200` for manual check.
|
||||||
|
|
||||||
|
Troubleshooting
|
||||||
|
---------------
|
||||||
|
| Symptom | Likely Cause | Action |
|
||||||
|
|---------|--------------|--------|
|
||||||
|
| No output on UART | Pi serial console still enabled | Disable login shell on serial via `raspi-config` |
|
||||||
|
| Garbled characters | Baud mismatch | Ensure both sides at 115200 |
|
||||||
|
| Only EVENT:START appears | Backend not sending newline | Ensure commands end with `\n` |
|
||||||
|
| USB port not found | Missing udev permissions | Add user to `dialout` (Linux) or run with proper permissions |
|
||||||
|
| Command times out | Long step count | Consider splitting into smaller STEP commands |
|
||||||
|
|
||||||
|
Extending the firmware
|
||||||
|
----------------------
|
||||||
|
- Add new command parsing in `src/main.cpp`.
|
||||||
|
- Emit `EVENT:RECEIVED <cmd>` when a command is received.
|
||||||
|
- Emit `EVENT:COMPLETED <...>` once the command finishes (or `EVENT:COMPLETED ERROR: ...` on failure).
|
||||||
|
- Update this README with any new event formats.
|
||||||
|
|
||||||
|
Files of interest
|
||||||
|
-----------------
|
||||||
|
- `src/main.cpp` — serial parser, command dispatch, and event emission.
|
||||||
|
- `src/ULN2003Stepper.h`, `src/Stepper.h` — stepper implementation.
|
||||||
|
- `src/schafkopf-bot.cpp` — helper logic; references globals via `extern`.
|
||||||
|
|
||||||
|
Notes for a Copilot implementer
|
||||||
|
------------------------------
|
||||||
|
- Preserve the exact prefixes (`EVENT:RECEIVED`, `EVENT:COMPLETED`, `STATUS:`).
|
||||||
|
- Add unit tests for the backend parser to assert the event shapes.
|
||||||
|
- Keep CLI/manual examples minimal and copyable.
|
||||||
|
- When adding new commands, document: syntax, EVENT:COMPLETED form, error cases, sample sequence.
|
||||||
|
- Keep the README as the single source of truth for the serial protocol.
|
||||||
|
|
||||||
---
|
---
|
||||||
This README is intended for another developer or Copilot to implement further changes or features.
|
This README is intended as the definitive reference for implementing the TypeScript backend and for extending the Pico firmware. Follow the message formats exactly to avoid breaking existing parsers.
|
||||||
|
|||||||
@@ -1,50 +1,111 @@
|
|||||||
|
// Supports two transport modes:
|
||||||
|
// 1. USB CDC (Serial) when connected over USB (preferred)
|
||||||
|
// 2. Hardware UART0 (Serial1) on GPIO pins for headless Raspberry Pi connection
|
||||||
|
// Wiring for UART mode (3.3V logic only):
|
||||||
|
// Pi GPIO14 (TXD0, physical pin 8) --> Pico GP1 (UART0 RX)
|
||||||
|
// Pi GPIO15 (RXD0, physical pin 10) <-- Pico GP0 (UART0 TX)
|
||||||
|
// Pi GND (any) <--------------------> Pico GND
|
||||||
|
// Optionally power Pico via USB. If powering from Pi 3V3, DO NOT also power via USB simultaneously.
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "Stepper.h"
|
#include "Stepper.h"
|
||||||
#include "ULN2003Stepper.h"
|
#include "ULN2003Stepper.h"
|
||||||
|
|
||||||
|
|
||||||
ULN2003Stepper driver1({18, 19, 20, 21}, 4096);
|
ULN2003Stepper driver1({18, 19, 20, 21}, 4096);
|
||||||
int revSteps = 0;
|
int revSteps = 0;
|
||||||
|
|
||||||
|
// Generic IO pointers (assigned to USB Serial or Serial1 at runtime)
|
||||||
|
Stream* serialIn = nullptr; // for available()/read()
|
||||||
|
Print* serialOut = nullptr; // for print()/println()
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
|
// Start USB CDC first
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (!Serial && (millis() - start) < 2000UL) {
|
||||||
|
// wait up to 2 seconds for host to open USB (non-blocking fallback)
|
||||||
|
}
|
||||||
|
if (Serial) {
|
||||||
|
serialIn = &Serial;
|
||||||
|
serialOut = &Serial;
|
||||||
|
Serial.println("INFO: Using USB CDC Serial");
|
||||||
|
} else {
|
||||||
|
// Fall back to UART0 on GPIO0 (TX) / GPIO1 (RX)
|
||||||
|
Serial1.begin(115200); // default pins for UART0 in Arduino-Pico core
|
||||||
|
serialIn = &Serial1;
|
||||||
|
serialOut = &Serial1;
|
||||||
|
Serial1.println("INFO: Using UART0 (Serial1) on GP0(TX)/GP1(RX)");
|
||||||
|
}
|
||||||
revSteps = driver1.get_steps_per_rev();
|
revSteps = driver1.get_steps_per_rev();
|
||||||
Serial.print("Steps: ");
|
serialOut->print("EVENT:START STEPS_PER_REV ");
|
||||||
Serial.println(revSteps);
|
serialOut->println(revSteps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper: trim leading/trailing whitespace
|
||||||
|
static String trim(const String &s) {
|
||||||
|
int start = 0;
|
||||||
|
int end = s.length() - 1;
|
||||||
|
while (start <= end && isspace(s.charAt(start))) start++;
|
||||||
|
while (end >= start && isspace(s.charAt(end))) end--;
|
||||||
|
if (end < start) return String("");
|
||||||
|
return s.substring(start, end + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
|
if (!serialIn || !serialOut) return; // safety
|
||||||
static String input = "";
|
static String input = "";
|
||||||
while (Serial.available()) {
|
while (serialIn->available()) {
|
||||||
char c = Serial.read();
|
char c = serialIn->read();
|
||||||
if (c == '\n' || c == '\r') {
|
if (c == '\n' || c == '\r') {
|
||||||
if (input.length() > 0) {
|
String cmd = trim(input);
|
||||||
Serial.print("Received: ");
|
if (cmd.length() > 0) {
|
||||||
Serial.println(input);
|
// Emit event: received
|
||||||
|
serialOut->print("EVENT:RECEIVED ");
|
||||||
|
serialOut->println(cmd);
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
String result = "OK";
|
||||||
|
|
||||||
// Parse command
|
// Parse command
|
||||||
if (input.startsWith("STEP ")) {
|
if (cmd.startsWith("STEP ")) {
|
||||||
int idx1 = input.indexOf(' ');
|
int idx1 = cmd.indexOf(' ');
|
||||||
int idx2 = input.indexOf(' ', idx1 + 1);
|
int idx2 = cmd.indexOf(' ', idx1 + 1);
|
||||||
int steps = input.substring(idx1 + 1, idx2).toInt();
|
if (idx1 != -1 && idx2 != -1) {
|
||||||
int dir = input.substring(idx2 + 1).toInt();
|
int steps = cmd.substring(idx1 + 1, idx2).toInt();
|
||||||
|
int dir = cmd.substring(idx2 + 1).toInt();
|
||||||
|
// execute
|
||||||
driver1.step(steps, dir != 0);
|
driver1.step(steps, dir != 0);
|
||||||
Serial.print("Motor moved ");
|
// report completion
|
||||||
Serial.print(steps);
|
serialOut->print("EVENT:COMPLETED STEP ");
|
||||||
Serial.print(" steps in direction ");
|
serialOut->print(steps);
|
||||||
Serial.println(dir);
|
serialOut->print(" ");
|
||||||
} else if (input.startsWith("SPEED ")) {
|
serialOut->println(dir);
|
||||||
int delay_us = input.substring(6).toInt();
|
|
||||||
driver1.setSpeed(delay_us);
|
|
||||||
Serial.print("Speed set to ");
|
|
||||||
Serial.println(delay_us);
|
|
||||||
} else if (input.startsWith("LOG ")) {
|
|
||||||
Serial.print("LOG: ");
|
|
||||||
Serial.println(input.substring(4));
|
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Unknown command");
|
success = false;
|
||||||
|
result = "ERROR: malformed STEP command";
|
||||||
|
}
|
||||||
|
} else if (cmd.startsWith("SPEED ")) {
|
||||||
|
int delay_us = cmd.substring(6).toInt();
|
||||||
|
driver1.setSpeed(delay_us);
|
||||||
|
serialOut->print("EVENT:COMPLETED SPEED ");
|
||||||
|
serialOut->println(delay_us);
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
|
result = "ERROR: unknown command";
|
||||||
|
serialOut->print("EVENT:COMPLETED ");
|
||||||
|
serialOut->println(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// always print a short status summary
|
||||||
|
if (success) {
|
||||||
|
serialOut->print("STATUS: ");
|
||||||
|
serialOut->println("OK");
|
||||||
|
} else {
|
||||||
|
serialOut->print("STATUS: ");
|
||||||
|
serialOut->println(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
input = "";
|
input = "";
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
input += c;
|
input += c;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user