Initial docs site: Astro/Starlight with caddy-docker-proxy
- Starlight documentation for mcnanovna and mcpositioner - 19 pages covering tools, prompts, hardware, and tutorials - Docker deployment with dev/prod modes - Makefile for docker compose management - Custom SVG logos and hero illustration
This commit is contained in:
commit
e21219be8d
8
.env.example
Normal file
8
.env.example
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Project identifier (prevents container name collisions)
|
||||||
|
COMPOSE_PROJECT=mcnanovna-docs
|
||||||
|
|
||||||
|
# Domain for caddy-docker-proxy
|
||||||
|
DOMAIN=mcnanovna.l.zmesh.systems
|
||||||
|
|
||||||
|
# Mode: prod or dev (used by Makefile to select compose files)
|
||||||
|
MODE=prod
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Astro
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# Environment (contains local overrides)
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
68
Dockerfile
Normal file
68
Dockerfile
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Multi-stage Dockerfile for Astro/Starlight docs
|
||||||
|
# Supports both dev (hot-reload) and prod (static) modes
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 1: Base with Node.js
|
||||||
|
# =============================================================================
|
||||||
|
FROM node:22-slim AS base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Disable Astro telemetry
|
||||||
|
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 2: Install dependencies
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS deps
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 3: Development server (hot-reload)
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS dev
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose Vite dev server port
|
||||||
|
EXPOSE 4321
|
||||||
|
|
||||||
|
# Dev server with host binding for container access
|
||||||
|
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 4: Build static site
|
||||||
|
# =============================================================================
|
||||||
|
FROM base AS builder
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Stage 5: Production with Caddy
|
||||||
|
# =============================================================================
|
||||||
|
FROM caddy:2-alpine AS prod
|
||||||
|
|
||||||
|
# Copy built static files
|
||||||
|
COPY --from=builder /app/dist /srv
|
||||||
|
|
||||||
|
# Simple Caddyfile for static file serving
|
||||||
|
# (caddy-docker-proxy handles TLS and reverse proxy externally)
|
||||||
|
COPY <<EOF /etc/caddy/Caddyfile
|
||||||
|
:80 {
|
||||||
|
root * /srv
|
||||||
|
file_server
|
||||||
|
try_files {path} {path}/ /index.html
|
||||||
|
encode gzip
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
@static path *.js *.css *.woff2 *.png *.svg *.jpg *.ico
|
||||||
|
header @static Cache-Control "public, max-age=31536000, immutable"
|
||||||
|
|
||||||
|
# Don't cache HTML
|
||||||
|
@html path *.html /
|
||||||
|
header @html Cache-Control "no-cache, no-store, must-revalidate"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
77
Makefile
Normal file
77
Makefile
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# mcnanovna docs - Makefile for docker compose management
|
||||||
|
# Usage:
|
||||||
|
# make up - Start production (static site)
|
||||||
|
# make dev - Start development (hot-reload)
|
||||||
|
# make down - Stop containers
|
||||||
|
# make logs - Follow container logs
|
||||||
|
# make build - Rebuild container images
|
||||||
|
# make shell - Open shell in running container
|
||||||
|
|
||||||
|
.PHONY: up down logs build rebuild shell dev clean help
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
include .env
|
||||||
|
export
|
||||||
|
|
||||||
|
# Compose command shortcuts
|
||||||
|
COMPOSE := docker compose
|
||||||
|
COMPOSE_DEV := docker compose -f docker-compose.yml -f docker-compose.dev.yml
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "mcnanovna docs - Astro/Starlight documentation site"
|
||||||
|
@echo ""
|
||||||
|
@echo "Usage:"
|
||||||
|
@echo " make up Start production (static Caddy server)"
|
||||||
|
@echo " make dev Start development (Vite hot-reload)"
|
||||||
|
@echo " make down Stop all containers"
|
||||||
|
@echo " make logs Follow container logs"
|
||||||
|
@echo " make build Build production image"
|
||||||
|
@echo " make rebuild Force rebuild (no cache)"
|
||||||
|
@echo " make shell Open shell in running container"
|
||||||
|
@echo " make clean Remove containers, images, volumes"
|
||||||
|
@echo ""
|
||||||
|
@echo "Environment:"
|
||||||
|
@echo " DOMAIN = $(DOMAIN)"
|
||||||
|
@echo " MODE = $(MODE)"
|
||||||
|
|
||||||
|
# Production mode
|
||||||
|
up: .env
|
||||||
|
$(COMPOSE) up -d
|
||||||
|
$(COMPOSE) logs -f
|
||||||
|
|
||||||
|
# Development mode with hot-reload
|
||||||
|
dev: .env
|
||||||
|
$(COMPOSE_DEV) up -d --build
|
||||||
|
$(COMPOSE_DEV) logs -f
|
||||||
|
|
||||||
|
# Stop containers
|
||||||
|
down:
|
||||||
|
$(COMPOSE) down
|
||||||
|
$(COMPOSE_DEV) down 2>/dev/null || true
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
logs:
|
||||||
|
$(COMPOSE) logs -f
|
||||||
|
|
||||||
|
# Build image
|
||||||
|
build:
|
||||||
|
$(COMPOSE) build
|
||||||
|
|
||||||
|
# Rebuild without cache
|
||||||
|
rebuild:
|
||||||
|
$(COMPOSE) build --no-cache
|
||||||
|
|
||||||
|
# Shell into running container
|
||||||
|
shell:
|
||||||
|
$(COMPOSE) exec docs sh
|
||||||
|
|
||||||
|
# Clean everything
|
||||||
|
clean:
|
||||||
|
$(COMPOSE) down -v --rmi local
|
||||||
|
$(COMPOSE_DEV) down -v --rmi local 2>/dev/null || true
|
||||||
|
|
||||||
|
# Create .env from example if missing
|
||||||
|
.env:
|
||||||
|
@echo "Creating .env from .env.example..."
|
||||||
|
@cp .env.example .env
|
||||||
87
astro.config.mjs
Normal file
87
astro.config.mjs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import starlight from '@astrojs/starlight';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: 'https://mcnanovna.l.zmesh.systems',
|
||||||
|
telemetry: false,
|
||||||
|
devToolbar: { enabled: false },
|
||||||
|
integrations: [
|
||||||
|
starlight({
|
||||||
|
title: 'mcnanovna',
|
||||||
|
description: 'MCP servers for NanoVNA control and antenna positioner automation',
|
||||||
|
logo: {
|
||||||
|
dark: './src/assets/logo-dark.svg',
|
||||||
|
light: './src/assets/logo-light.svg',
|
||||||
|
replacesTitle: false,
|
||||||
|
},
|
||||||
|
social: {
|
||||||
|
github: 'https://git.supported.systems/rf/mcnanovna',
|
||||||
|
},
|
||||||
|
sidebar: [
|
||||||
|
{
|
||||||
|
label: 'Getting Started',
|
||||||
|
items: [
|
||||||
|
{ label: 'Introduction', slug: '' },
|
||||||
|
{ label: 'Quick Start', slug: 'getting-started/quickstart' },
|
||||||
|
{ label: 'Installation', slug: 'getting-started/installation' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mcnanovna',
|
||||||
|
items: [
|
||||||
|
{ label: 'Overview', slug: 'mcnanovna/overview' },
|
||||||
|
{ label: 'Tool Reference', slug: 'mcnanovna/tools' },
|
||||||
|
{ label: 'Prompts', slug: 'mcnanovna/prompts' },
|
||||||
|
{ label: 'Web UI', slug: 'mcnanovna/webui' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'mcpositioner',
|
||||||
|
items: [
|
||||||
|
{ label: 'Overview', slug: 'mcpositioner/overview' },
|
||||||
|
{ label: 'Tool Reference', slug: 'mcpositioner/tools' },
|
||||||
|
{ label: 'Prompts', slug: 'mcpositioner/prompts' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hardware',
|
||||||
|
items: [
|
||||||
|
{ label: 'Positioner Build', slug: 'hardware/positioner-build' },
|
||||||
|
{ label: 'Wiring Diagram', slug: 'hardware/wiring' },
|
||||||
|
{ label: 'Firmware', slug: 'hardware/firmware' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Tutorials',
|
||||||
|
items: [
|
||||||
|
{ label: '3D Pattern Measurement', slug: 'tutorials/pattern-measurement' },
|
||||||
|
{ label: 'VNA Calibration', slug: 'tutorials/calibration' },
|
||||||
|
{ label: 'Antenna Analysis', slug: 'tutorials/antenna-analysis' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Reference',
|
||||||
|
items: [
|
||||||
|
{ label: 'HTTP API (ESP32)', slug: 'reference/http-api' },
|
||||||
|
{ label: 'Pattern Formats', slug: 'reference/pattern-formats' },
|
||||||
|
{ label: 'Ham Bands', slug: 'reference/ham-bands' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customCss: ['./src/styles/custom.css'],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
vite: {
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
// Only configure HMR for reverse proxy when explicitly set
|
||||||
|
...(process.env.VITE_HMR_HOST && {
|
||||||
|
hmr: {
|
||||||
|
host: process.env.VITE_HMR_HOST,
|
||||||
|
protocol: 'wss',
|
||||||
|
clientPort: 443,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
27
docker-compose.dev.yml
Normal file
27
docker-compose.dev.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Development overrides - hot-reload with volume mounts
|
||||||
|
# Usage: docker compose -f docker-compose.yml -f docker-compose.dev.yml up
|
||||||
|
|
||||||
|
services:
|
||||||
|
docs:
|
||||||
|
build:
|
||||||
|
target: dev
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- /app/node_modules # Anonymous volume to preserve node_modules
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
- ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
- VITE_HMR_HOST=${DOMAIN:-mcnanovna.l.zmesh.systems}
|
||||||
|
labels:
|
||||||
|
# Override reverse proxy to Vite dev server port
|
||||||
|
caddy.reverse_proxy: "{{upstreams 4321}}"
|
||||||
|
|
||||||
|
# WebSocket support for Vite HMR
|
||||||
|
caddy.reverse_proxy.flush_interval: "-1"
|
||||||
|
caddy.reverse_proxy.transport: "http"
|
||||||
|
caddy.reverse_proxy.transport.read_timeout: "0"
|
||||||
|
caddy.reverse_proxy.transport.write_timeout: "0"
|
||||||
|
caddy.reverse_proxy.transport.keepalive: "5m"
|
||||||
|
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
|
||||||
|
caddy.reverse_proxy.stream_timeout: "24h"
|
||||||
|
caddy.reverse_proxy.stream_close_delay: "5s"
|
||||||
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
services:
|
||||||
|
docs:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: prod
|
||||||
|
container_name: ${COMPOSE_PROJECT:-mcnanovna-docs}
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
environment:
|
||||||
|
- ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
labels:
|
||||||
|
# caddy-docker-proxy configuration
|
||||||
|
caddy: ${DOMAIN:-mcnanovna.l.zmesh.systems}
|
||||||
|
caddy.reverse_proxy: "{{upstreams 80}}"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
6506
package-lock.json
generated
Normal file
6506
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "mcnanovna-docs",
|
||||||
|
"version": "2026.02.04",
|
||||||
|
"description": "Documentation for mcnanovna and mcpositioner MCP servers",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/starlight": "^0.32.4",
|
||||||
|
"astro": "^5.17.1",
|
||||||
|
"sharp": "^0.34.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/assets/hero.svg
Normal file
45
src/assets/hero.svg
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 300" fill="none">
|
||||||
|
<!-- VNA Device -->
|
||||||
|
<rect x="50" y="100" width="120" height="80" rx="8" stroke="#3182ce" stroke-width="2" fill="#1a365d" fill-opacity="0.3"/>
|
||||||
|
<text x="110" y="130" text-anchor="middle" fill="#63b3ed" font-family="monospace" font-size="10">NanoVNA</text>
|
||||||
|
|
||||||
|
<!-- VNA Screen with waveform -->
|
||||||
|
<rect x="60" y="140" width="100" height="30" rx="2" fill="#0d1117"/>
|
||||||
|
<polyline points="65,155 75,150 85,160 95,152 105,158 115,153 125,157 135,155 145,156 155,155"
|
||||||
|
stroke="#4ade80" stroke-width="1.5" fill="none"/>
|
||||||
|
|
||||||
|
<!-- USB Connection -->
|
||||||
|
<line x1="170" y1="140" x2="200" y2="140" stroke="#718096" stroke-width="2" stroke-dasharray="4,2"/>
|
||||||
|
|
||||||
|
<!-- Computer/Claude -->
|
||||||
|
<rect x="200" y="110" width="80" height="60" rx="4" stroke="#3182ce" stroke-width="2" fill="#1a365d" fill-opacity="0.3"/>
|
||||||
|
<text x="240" y="145" text-anchor="middle" fill="#63b3ed" font-family="monospace" font-size="10">Claude</text>
|
||||||
|
|
||||||
|
<!-- WiFi waves to positioner -->
|
||||||
|
<path d="M 280 140 Q 300 130 320 140" stroke="#718096" stroke-width="1.5" fill="none"/>
|
||||||
|
<path d="M 285 140 Q 300 135 315 140" stroke="#718096" stroke-width="1.5" fill="none"/>
|
||||||
|
<path d="M 290 140 Q 300 137 310 140" stroke="#718096" stroke-width="1.5" fill="none"/>
|
||||||
|
|
||||||
|
<!-- Positioner -->
|
||||||
|
<g transform="translate(320, 80)">
|
||||||
|
<!-- Base -->
|
||||||
|
<ellipse cx="30" cy="100" rx="35" ry="10" fill="#2d3748"/>
|
||||||
|
<!-- Vertical axis -->
|
||||||
|
<rect x="25" y="40" width="10" height="60" fill="#4a5568"/>
|
||||||
|
<!-- Horizontal arm -->
|
||||||
|
<rect x="20" y="35" width="40" height="8" fill="#4a5568"/>
|
||||||
|
<!-- Antenna -->
|
||||||
|
<line x1="60" y1="20" x2="60" y2="50" stroke="#f6ad55" stroke-width="3"/>
|
||||||
|
<line x1="50" y1="30" x2="70" y2="30" stroke="#f6ad55" stroke-width="2"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- 3D Pattern sphere hint -->
|
||||||
|
<circle cx="350" cy="200" r="40" stroke="#3182ce" stroke-width="1" fill="none" stroke-dasharray="3,3"/>
|
||||||
|
<ellipse cx="350" cy="200" rx="40" ry="15" stroke="#3182ce" stroke-width="1" fill="none"/>
|
||||||
|
<ellipse cx="350" cy="200" rx="15" ry="40" stroke="#3182ce" stroke-width="1" fill="none"/>
|
||||||
|
|
||||||
|
<!-- Labels -->
|
||||||
|
<text x="110" y="200" text-anchor="middle" fill="#a0aec0" font-family="sans-serif" font-size="9">USB Serial</text>
|
||||||
|
<text x="300" y="165" text-anchor="middle" fill="#a0aec0" font-family="sans-serif" font-size="9">WiFi HTTP</text>
|
||||||
|
<text x="350" y="260" text-anchor="middle" fill="#a0aec0" font-family="sans-serif" font-size="9">3D Pattern</text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
8
src/assets/logo-dark.svg
Normal file
8
src/assets/logo-dark.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect x="2" y="8" width="28" height="16" rx="2" stroke="#63b3ed" stroke-width="2"/>
|
||||||
|
<line x1="6" y1="16" x2="10" y2="16" stroke="#63b3ed" stroke-width="2"/>
|
||||||
|
<polyline points="10,16 12,12 14,20 16,14 18,18 20,16" stroke="#63b3ed" stroke-width="1.5" fill="none"/>
|
||||||
|
<line x1="20" y1="16" x2="26" y2="16" stroke="#63b3ed" stroke-width="2"/>
|
||||||
|
<circle cx="6" cy="16" r="1.5" fill="#63b3ed"/>
|
||||||
|
<circle cx="26" cy="16" r="1.5" fill="#63b3ed"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 525 B |
8
src/assets/logo-light.svg
Normal file
8
src/assets/logo-light.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
|
||||||
|
<rect x="2" y="8" width="28" height="16" rx="2" stroke="#2b6cb0" stroke-width="2"/>
|
||||||
|
<line x1="6" y1="16" x2="10" y2="16" stroke="#2b6cb0" stroke-width="2"/>
|
||||||
|
<polyline points="10,16 12,12 14,20 16,14 18,18 20,16" stroke="#2b6cb0" stroke-width="1.5" fill="none"/>
|
||||||
|
<line x1="20" y1="16" x2="26" y2="16" stroke="#2b6cb0" stroke-width="2"/>
|
||||||
|
<circle cx="6" cy="16" r="1.5" fill="#2b6cb0"/>
|
||||||
|
<circle cx="26" cy="16" r="1.5" fill="#2b6cb0"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 525 B |
7
src/content.config.ts
Normal file
7
src/content.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineCollection } from 'astro:content';
|
||||||
|
import { docsLoader } from '@astrojs/starlight/loaders';
|
||||||
|
import { docsSchema } from '@astrojs/starlight/schema';
|
||||||
|
|
||||||
|
export const collections = {
|
||||||
|
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
|
||||||
|
};
|
||||||
108
src/content/docs/getting-started/installation.mdx
Normal file
108
src/content/docs/getting-started/installation.mdx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
title: Installation
|
||||||
|
description: Detailed installation options for mcnanovna and mcpositioner
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## mcnanovna
|
||||||
|
|
||||||
|
### From PyPI (Recommended)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Claude Code">
|
||||||
|
```bash
|
||||||
|
claude mcp add mcnanovna -- uvx mcnanovna
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Standalone">
|
||||||
|
```bash
|
||||||
|
# Run directly with uvx
|
||||||
|
uvx mcnanovna
|
||||||
|
|
||||||
|
# Or install globally
|
||||||
|
pipx install mcnanovna
|
||||||
|
mcnanovna
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.supported.systems/rf/mcnanovna.git
|
||||||
|
cd mcnanovna
|
||||||
|
uv sync
|
||||||
|
uv run mcnanovna
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Web UI
|
||||||
|
|
||||||
|
The optional 3D radiation pattern viewer requires additional dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From source
|
||||||
|
uv sync --extra webui
|
||||||
|
MCNANOVNA_WEB_PORT=8080 uv run mcnanovna
|
||||||
|
|
||||||
|
# Open http://localhost:8080 in your browser
|
||||||
|
```
|
||||||
|
|
||||||
|
## mcpositioner
|
||||||
|
|
||||||
|
### From PyPI
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Claude Code">
|
||||||
|
```bash
|
||||||
|
claude mcp add mcpositioner -- uvx mcpositioner
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Standalone">
|
||||||
|
```bash
|
||||||
|
uvx mcpositioner
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.supported.systems/rf/mcpositioner.git
|
||||||
|
cd mcpositioner
|
||||||
|
uv sync
|
||||||
|
uv run mcpositioner
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
mcpositioner requires the ESP32 positioner hardware. See [Hardware Build](/hardware/positioner-build/) for assembly instructions.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
### mcnanovna
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `MCNANOVNA_WEB_PORT` | (disabled) | Port for 3D pattern web UI |
|
||||||
|
|
||||||
|
### mcpositioner
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `MCPOSITIONER_HOST` | `positioner.local` | ESP32 hostname or IP |
|
||||||
|
|
||||||
|
## Both Servers Together
|
||||||
|
|
||||||
|
For automated 3D antenna pattern measurement, run both servers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add both to Claude Code
|
||||||
|
claude mcp add mcnanovna -- uvx mcnanovna
|
||||||
|
claude mcp add mcpositioner -- uvx mcpositioner
|
||||||
|
|
||||||
|
# Restart Claude Code to load both
|
||||||
|
claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use the `measure_antenna_range` prompt to run automated pattern sweeps.
|
||||||
70
src/content/docs/getting-started/quickstart.mdx
Normal file
70
src/content/docs/getting-started/quickstart.mdx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
---
|
||||||
|
title: Quick Start
|
||||||
|
description: Get mcnanovna running in 5 minutes
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Claude Code CLI installed
|
||||||
|
- NanoVNA-H connected via USB
|
||||||
|
- Python 3.11+ (handled automatically by uvx)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. **Add the MCP server to Claude Code**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude mcp add mcnanovna -- uvx mcnanovna
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start a new Claude Code session**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
claude
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Ask Claude to use your VNA**
|
||||||
|
|
||||||
|
Try these prompts:
|
||||||
|
- "Scan my antenna from 144 to 148 MHz"
|
||||||
|
- "What's the SWR at 145 MHz?"
|
||||||
|
- "Analyze this filter's frequency response"
|
||||||
|
- "Capture a screenshot of the VNA display"
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
The VNA auto-connects on first tool call. No configuration needed if using default USB settings.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Verify Connection
|
||||||
|
|
||||||
|
Ask Claude: "Get VNA info"
|
||||||
|
|
||||||
|
You should see device details like firmware version, serial number, and frequency range.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Install mcpositioner](/mcpositioner/overview/) for automated antenna measurements
|
||||||
|
- [Run a calibration](/tutorials/calibration/) for accurate measurements
|
||||||
|
- [Explore all 78 tools](/mcnanovna/tools/)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### VNA not detected
|
||||||
|
|
||||||
|
1. Check USB connection
|
||||||
|
2. Verify device appears as `/dev/ttyACM0` (Linux) or COM port (Windows)
|
||||||
|
3. Check permissions: `sudo usermod -aG dialout $USER` (Linux)
|
||||||
|
|
||||||
|
### Permission denied
|
||||||
|
|
||||||
|
On Linux, add your user to the dialout group:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG dialout $USER
|
||||||
|
# Log out and back in for changes to take effect
|
||||||
|
```
|
||||||
166
src/content/docs/hardware/firmware.mdx
Normal file
166
src/content/docs/hardware/firmware.mdx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
title: Firmware
|
||||||
|
description: Build and flash the ESP32 positioner firmware
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [PlatformIO](https://platformio.org/) (CLI or VS Code extension)
|
||||||
|
- USB cable for ESP32
|
||||||
|
|
||||||
|
## Build and Flash
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. **Clone the repository**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.supported.systems/rf/mcpositioner.git
|
||||||
|
cd mcpositioner/firmware
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Build the firmware**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Flash to ESP32**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run -t upload
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Monitor serial output** (optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio device monitor
|
||||||
|
```
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `include/config.h` before building:
|
||||||
|
|
||||||
|
### WiFi Settings
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#define WIFI_SSID "your-network"
|
||||||
|
#define WIFI_PASSWORD "your-password"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Motor Parameters
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Steps per revolution (including microstepping)
|
||||||
|
#define THETA_STEPS_PER_REV 3200 // 200 * 16 microstepping
|
||||||
|
#define PHI_STEPS_PER_REV 3200
|
||||||
|
|
||||||
|
// Gear ratios (if using reduction gearing)
|
||||||
|
#define THETA_GEAR_RATIO 1.0
|
||||||
|
#define PHI_GEAR_RATIO 1.0
|
||||||
|
|
||||||
|
// Motor current (mA)
|
||||||
|
#define MOTOR_CURRENT_MA 800
|
||||||
|
```
|
||||||
|
|
||||||
|
### TMC2209 Settings
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// UART addresses
|
||||||
|
#define THETA_TMC_ADDR 0x00
|
||||||
|
#define PHI_TMC_ADDR 0x01
|
||||||
|
|
||||||
|
// StallGuard threshold (lower = more sensitive)
|
||||||
|
#define STALL_VALUE 50
|
||||||
|
|
||||||
|
// Homing speed (steps/sec)
|
||||||
|
#define HOMING_SPEED 500
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pin Assignments
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Theta axis
|
||||||
|
#define THETA_STEP_PIN 16
|
||||||
|
#define THETA_DIR_PIN 17
|
||||||
|
#define THETA_EN_PIN 4
|
||||||
|
|
||||||
|
// Phi axis
|
||||||
|
#define PHI_STEP_PIN 21
|
||||||
|
#define PHI_DIR_PIN 22
|
||||||
|
#define PHI_EN_PIN 23
|
||||||
|
|
||||||
|
// Shared UART
|
||||||
|
#define TMC_UART_TX 18
|
||||||
|
#define TMC_UART_RX 19
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
|
||||||
|
The firmware exposes these endpoints at `http://positioner.local`:
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/status` | GET | Current position and state |
|
||||||
|
| `/move` | POST | Absolute move: `{"theta_deg": 90, "phi_deg": 45}` |
|
||||||
|
| `/move/relative` | POST | Relative move: `{"d_theta": 5, "d_phi": 10}` |
|
||||||
|
| `/home` | POST | Homing: `{"axis": "both"}` |
|
||||||
|
| `/stop` | POST | Emergency stop |
|
||||||
|
| `/config` | GET/POST | Motion parameters |
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
The mDNS name `positioner.local` is configurable via `MDNS_HOSTNAME` in config.h.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
The firmware uses these PlatformIO libraries (auto-installed):
|
||||||
|
|
||||||
|
| Library | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| AccelStepper | Smooth acceleration profiles |
|
||||||
|
| TMCStepper | TMC2209 UART configuration |
|
||||||
|
| ESPAsyncWebServer | HTTP API |
|
||||||
|
| ArduinoJson | JSON parsing |
|
||||||
|
| ESPmDNS | Network discovery |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Can't connect to WiFi
|
||||||
|
|
||||||
|
- Check SSID and password in config.h
|
||||||
|
- Verify ESP32 is within range
|
||||||
|
- Check serial monitor for connection status
|
||||||
|
|
||||||
|
### mDNS not working
|
||||||
|
|
||||||
|
- Some networks block mDNS
|
||||||
|
- Use IP address directly (shown in serial monitor on boot)
|
||||||
|
- Set `MCPOSITIONER_HOST` environment variable
|
||||||
|
|
||||||
|
### Motors don't respond
|
||||||
|
|
||||||
|
- Check UART wiring (TX/RX may be swapped)
|
||||||
|
- Verify TMC2209 addresses match config
|
||||||
|
- Check motor power supply
|
||||||
|
|
||||||
|
### StallGuard unreliable
|
||||||
|
|
||||||
|
- Adjust `STALL_VALUE` (lower = more sensitive)
|
||||||
|
- Increase motor current
|
||||||
|
- Decrease homing speed
|
||||||
|
- Ensure mechanical stops are solid
|
||||||
|
|
||||||
|
## OTA Updates
|
||||||
|
|
||||||
|
The firmware supports over-the-air updates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pio run -t upload --upload-port positioner.local
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
OTA requires the ESP32 to be on the same network as your computer.
|
||||||
|
</Aside>
|
||||||
117
src/content/docs/hardware/positioner-build.mdx
Normal file
117
src/content/docs/hardware/positioner-build.mdx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
title: Positioner Build Guide
|
||||||
|
description: Build the ESP32 dual-axis antenna positioner
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Bill of Materials
|
||||||
|
|
||||||
|
| Qty | Component | Notes |
|
||||||
|
|-----|-----------|-------|
|
||||||
|
| 1 | ESP32 DevKit | Any variant with enough GPIO |
|
||||||
|
| 2 | TMC2209 stepper driver | UART mode, StallGuard support |
|
||||||
|
| 2 | NEMA 17 stepper motor | 0.9° or 1.8° step angle |
|
||||||
|
| 1 | 24V power supply | 3A+ for motors |
|
||||||
|
| 1 | 5V regulator or USB | For ESP32 |
|
||||||
|
| - | Wire, connectors | See wiring diagram |
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
The TMC2209 must be in UART mode, not standalone mode. This enables StallGuard sensorless homing and runtime configuration.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Mechanical Assembly
|
||||||
|
|
||||||
|
The positioner needs two axes of rotation:
|
||||||
|
|
||||||
|
1. **Theta axis** (polar, 0-180°): Tilts the antenna from zenith to nadir
|
||||||
|
2. **Phi axis** (azimuth, 0-360°): Rotates the antenna around vertical axis
|
||||||
|
|
||||||
|
### Design Considerations
|
||||||
|
|
||||||
|
- **Cable routing**: Ensure cables can handle full rotation without tangling
|
||||||
|
- **Balance**: Center of gravity should be on the rotation axes
|
||||||
|
- **Rigidity**: Minimize wobble for accurate measurements
|
||||||
|
- **Range limits**: Mechanical stops for homing reference
|
||||||
|
|
||||||
|
### Mounting Options
|
||||||
|
|
||||||
|
| Approach | Pros | Cons |
|
||||||
|
|----------|------|------|
|
||||||
|
| 3D printed | Custom fit, cheap | Strength limits |
|
||||||
|
| Aluminum extrusion | Strong, adjustable | Heavier, more complex |
|
||||||
|
| PVC pipe | Very cheap, easy | Less precise |
|
||||||
|
|
||||||
|
## Electronics Assembly
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. **Mount TMC2209 drivers**
|
||||||
|
|
||||||
|
Install both drivers on a breakout board or custom PCB. Ensure proper heatsinking—these drivers can get hot under load.
|
||||||
|
|
||||||
|
2. **Wire motor connections**
|
||||||
|
|
||||||
|
| TMC2209 Pin | Motor Wire |
|
||||||
|
|-------------|------------|
|
||||||
|
| A1 | Coil A+ |
|
||||||
|
| A2 | Coil A- |
|
||||||
|
| B1 | Coil B+ |
|
||||||
|
| B2 | Coil B- |
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Never disconnect motors while powered—the back-EMF can damage the driver.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
3. **Wire UART connections**
|
||||||
|
|
||||||
|
Both drivers share the same UART bus but have different addresses:
|
||||||
|
- Theta driver: Address 0x00
|
||||||
|
- Phi driver: Address 0x01
|
||||||
|
|
||||||
|
See [Wiring Diagram](/hardware/wiring/) for full pinout.
|
||||||
|
|
||||||
|
4. **Power connections**
|
||||||
|
|
||||||
|
- 24V to driver VMOT pins (motor power)
|
||||||
|
- 5V/3.3V to driver VIO pins (logic power)
|
||||||
|
- ESP32 powered via USB or separate 5V regulator
|
||||||
|
|
||||||
|
5. **Test before mounting**
|
||||||
|
|
||||||
|
Flash the firmware and verify both motors respond before final assembly.
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Firmware Setup
|
||||||
|
|
||||||
|
See [Firmware](/hardware/firmware/) for build and flash instructions.
|
||||||
|
|
||||||
|
## Calibration
|
||||||
|
|
||||||
|
After assembly:
|
||||||
|
|
||||||
|
1. **Home both axes**: Run `positioner_home(axis="both")`
|
||||||
|
2. **Verify range**: Move to extremes and check for binding
|
||||||
|
3. **Tune StallGuard**: Adjust `STALL_VALUE` in `config.h` if homing is unreliable
|
||||||
|
4. **Set motion parameters**: Use `positioner_config()` to tune speed/accel
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Motor doesn't move
|
||||||
|
- Check power supply voltage (should be 24V)
|
||||||
|
- Verify UART communication (TX/RX wiring)
|
||||||
|
- Check TMC2209 address configuration
|
||||||
|
|
||||||
|
### Homing fails (doesn't detect stall)
|
||||||
|
- Increase motor current in `config.h`
|
||||||
|
- Decrease homing speed
|
||||||
|
- Adjust `STALL_VALUE` threshold
|
||||||
|
|
||||||
|
### Motors get hot
|
||||||
|
- Reduce motor current (if torque allows)
|
||||||
|
- Add heatsinks to TMC2209
|
||||||
|
- Reduce holding current when idle
|
||||||
|
|
||||||
|
### Position drift
|
||||||
|
- Check mechanical coupling (loose setscrews)
|
||||||
|
- Verify microstepping is consistent
|
||||||
|
- Check for missed steps (reduce speed/accel)
|
||||||
113
src/content/docs/hardware/wiring.mdx
Normal file
113
src/content/docs/hardware/wiring.mdx
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
title: Wiring Diagram
|
||||||
|
description: ESP32 to TMC2209 to stepper motor connections
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Pin Assignments
|
||||||
|
|
||||||
|
These are the default pin assignments in `firmware/include/config.h`:
|
||||||
|
|
||||||
|
### ESP32 to TMC2209 (Theta Axis)
|
||||||
|
|
||||||
|
| ESP32 Pin | TMC2209 Pin | Function |
|
||||||
|
|-----------|-------------|----------|
|
||||||
|
| GPIO 16 | STEP | Step pulse |
|
||||||
|
| GPIO 17 | DIR | Direction |
|
||||||
|
| GPIO 4 | EN | Enable (active low) |
|
||||||
|
| GPIO 18 | UART TX | TMC UART |
|
||||||
|
| GPIO 19 | UART RX | TMC UART |
|
||||||
|
|
||||||
|
### ESP32 to TMC2209 (Phi Axis)
|
||||||
|
|
||||||
|
| ESP32 Pin | TMC2209 Pin | Function |
|
||||||
|
|-----------|-------------|----------|
|
||||||
|
| GPIO 21 | STEP | Step pulse |
|
||||||
|
| GPIO 22 | DIR | Direction |
|
||||||
|
| GPIO 23 | EN | Enable (active low) |
|
||||||
|
| GPIO 18 | UART TX | Shared with theta |
|
||||||
|
| GPIO 19 | UART RX | Shared with theta |
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Both TMC2209 drivers share the same UART bus. They're distinguished by address (0x00 for theta, 0x01 for phi).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## TMC2209 Driver Wiring
|
||||||
|
|
||||||
|
### Power
|
||||||
|
|
||||||
|
| Pin | Connection |
|
||||||
|
|-----|------------|
|
||||||
|
| VMOT | 24V motor supply |
|
||||||
|
| GND (motor) | 24V ground |
|
||||||
|
| VIO | 3.3V (from ESP32) |
|
||||||
|
| GND (logic) | ESP32 ground |
|
||||||
|
|
||||||
|
### Motor Outputs
|
||||||
|
|
||||||
|
| Pin | Connection |
|
||||||
|
|-----|------------|
|
||||||
|
| A1, A2 | Motor coil A |
|
||||||
|
| B1, B2 | Motor coil B |
|
||||||
|
|
||||||
|
### UART Configuration
|
||||||
|
|
||||||
|
For UART mode, connect:
|
||||||
|
- **MS1**: To address selection (see address table)
|
||||||
|
- **MS2**: To GND for UART mode
|
||||||
|
- **PDN_UART**: To ESP32 UART TX/RX through 1kΩ resistor
|
||||||
|
|
||||||
|
#### TMC2209 Address Selection (MS1 pin)
|
||||||
|
|
||||||
|
| Address | MS1 | MS2 |
|
||||||
|
|---------|-----|-----|
|
||||||
|
| 0x00 | GND | GND |
|
||||||
|
| 0x01 | VIO | GND |
|
||||||
|
| 0x02 | GND | VIO |
|
||||||
|
| 0x03 | VIO | VIO |
|
||||||
|
|
||||||
|
## Schematic
|
||||||
|
|
||||||
|
Full KiCad schematics are in the repository:
|
||||||
|
|
||||||
|
```
|
||||||
|
mcpositioner/hardware/
|
||||||
|
├── positioner.kicad_pro # Project file
|
||||||
|
├── positioner.kicad_sch # Main schematic
|
||||||
|
├── positioner.kicad_sym # Custom symbols
|
||||||
|
└── sym-lib-table # Symbol library table
|
||||||
|
```
|
||||||
|
|
||||||
|
View online: [hardware/ on Gitea](https://git.supported.systems/rf/mcpositioner/src/branch/main/hardware)
|
||||||
|
|
||||||
|
## Power Supply Notes
|
||||||
|
|
||||||
|
### Motor Power (24V)
|
||||||
|
|
||||||
|
- Minimum 3A capacity for two NEMA 17 motors
|
||||||
|
- Add bulk capacitance (100-470µF) near drivers
|
||||||
|
- Keep motor power wiring short and thick
|
||||||
|
|
||||||
|
### Logic Power (3.3V)
|
||||||
|
|
||||||
|
- ESP32's 3.3V output can power both TMC2209 VIO pins
|
||||||
|
- Current draw is minimal (~10mA per driver)
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Never connect motor power (24V) to logic pins (3.3V). This will destroy the driver and possibly the ESP32.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Cable Considerations
|
||||||
|
|
||||||
|
### Motor Cables
|
||||||
|
|
||||||
|
- Use shielded cable if runs are long (>1m)
|
||||||
|
- Keep motor cables away from sensitive RF connections
|
||||||
|
- Twisted pairs reduce EMI
|
||||||
|
|
||||||
|
### Measurement Cables
|
||||||
|
|
||||||
|
- The antenna under test connects to the VNA via coax
|
||||||
|
- Ensure enough slack for full theta/phi rotation
|
||||||
|
- Consider using rotary joints for continuous rotation
|
||||||
59
src/content/docs/index.mdx
Normal file
59
src/content/docs/index.mdx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: mcnanovna
|
||||||
|
description: MCP servers for NanoVNA control and antenna positioner automation
|
||||||
|
template: splash
|
||||||
|
hero:
|
||||||
|
tagline: Give your AI direct control of RF test equipment
|
||||||
|
image:
|
||||||
|
file: ../../assets/hero.svg
|
||||||
|
actions:
|
||||||
|
- text: Get Started
|
||||||
|
link: /getting-started/quickstart/
|
||||||
|
icon: right-arrow
|
||||||
|
- text: View on Gitea
|
||||||
|
link: https://git.supported.systems/rf/mcnanovna
|
||||||
|
icon: external
|
||||||
|
variant: minimal
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Card, CardGrid } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## What is this?
|
||||||
|
|
||||||
|
Two MCP servers that let AI assistants control RF test equipment:
|
||||||
|
|
||||||
|
<CardGrid stagger>
|
||||||
|
<Card title="mcnanovna" icon="document">
|
||||||
|
Controls NanoVNA-H vector network analyzers over USB serial.
|
||||||
|
78 tools for sweeps, calibration, analysis, and 3D radiation patterns.
|
||||||
|
</Card>
|
||||||
|
<Card title="mcpositioner" icon="setting">
|
||||||
|
Controls ESP32 dual-axis antenna positioners over WiFi.
|
||||||
|
5 tools for stepper motor positioning and automated measurement grids.
|
||||||
|
</Card>
|
||||||
|
<Card title="Cross-Server Workflows" icon="rocket">
|
||||||
|
Both servers work together for automated 3D antenna pattern measurement.
|
||||||
|
The AI orchestrates positioning and VNA measurements across the grid.
|
||||||
|
</Card>
|
||||||
|
<Card title="Web UI" icon="laptop">
|
||||||
|
Optional Three.js 3D viewer for radiation patterns.
|
||||||
|
Real-time visualization as measurements are taken.
|
||||||
|
</Card>
|
||||||
|
</CardGrid>
|
||||||
|
|
||||||
|
## Quick Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add both servers to Claude Code
|
||||||
|
claude mcp add mcnanovna -- uvx mcnanovna
|
||||||
|
claude mcp add mcpositioner -- uvx mcpositioner
|
||||||
|
```
|
||||||
|
|
||||||
|
Then ask Claude to analyze your antenna, measure a filter, or run a 3D pattern sweep.
|
||||||
|
|
||||||
|
## Hardware
|
||||||
|
|
||||||
|
- **NanoVNA-H** or H4 (USB serial, auto-detected)
|
||||||
|
- **ESP32 positioner** (optional, for automated pattern measurement)
|
||||||
|
- 2x NEMA 17 steppers + TMC2209 drivers
|
||||||
|
- Firmware and KiCad schematics included
|
||||||
85
src/content/docs/mcnanovna/overview.mdx
Normal file
85
src/content/docs/mcnanovna/overview.mdx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title: mcnanovna Overview
|
||||||
|
description: MCP server for NanoVNA-H vector network analyzers
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
mcnanovna gives AI assistants direct control of NanoVNA-H vector network analyzers over USB serial. It exposes 78 MCP tools for frequency sweeps, S-parameter measurements, calibration, LCD capture, RF analysis, and 3D antenna radiation pattern visualization.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### Measurement
|
||||||
|
- Frequency sweeps with configurable start/stop/points
|
||||||
|
- S11 (reflection) and S21 (transmission) measurements
|
||||||
|
- Marker-based frequency analysis
|
||||||
|
- Real-time data streaming
|
||||||
|
|
||||||
|
### Calibration
|
||||||
|
- Full SOLT calibration workflow
|
||||||
|
- Save/recall calibration to device flash
|
||||||
|
- Calibration status verification
|
||||||
|
|
||||||
|
### Analysis
|
||||||
|
- SWR, return loss, impedance calculations
|
||||||
|
- Filter characterization (cutoffs, bandwidth, Q)
|
||||||
|
- Crystal parameter extraction
|
||||||
|
- TDR (time domain reflectometry)
|
||||||
|
- L/C component identification
|
||||||
|
- Impedance matching network design
|
||||||
|
|
||||||
|
### Radiation Patterns
|
||||||
|
- Analytical 3D patterns from S11 data
|
||||||
|
- Support for dipole, monopole, EFHW, loop, patch antennas
|
||||||
|
- Pattern import from CSV, EMCAR, NEC2, Touchstone S1P
|
||||||
|
- Optional Three.js web viewer
|
||||||
|
|
||||||
|
## Supported Hardware
|
||||||
|
|
||||||
|
| Device | Frequency Range | Notes |
|
||||||
|
|--------|----------------|-------|
|
||||||
|
| NanoVNA-H | 50 kHz - 900 MHz | Original hardware |
|
||||||
|
| NanoVNA-H4 | 50 kHz - 1.5 GHz | Extended range |
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
Other NanoVNA variants using the same USB serial protocol (VID 0x0483, PID 0x5740) should also work.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Claude Code │
|
||||||
|
│ │ │
|
||||||
|
│ MCP Protocol │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ mcnanovna server │ │
|
||||||
|
│ │ ┌─────────┐ ┌────────────┐ ┌─────────────┐ │ │
|
||||||
|
│ │ │ tools/ │ │ protocol.py│ │calculations │ │ │
|
||||||
|
│ │ │ 8 mixins│ │ USB serial│ │ S-param │ │ │
|
||||||
|
│ │ └────┬────┘ └─────┬──────┘ │ math │ │ │
|
||||||
|
│ │ │ │ └─────────────┘ │ │
|
||||||
|
│ │ ▼ ▼ │ │
|
||||||
|
│ │ ┌──────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ NanoVNA class │ │ │
|
||||||
|
│ │ │ (connection lifecycle, auto-reconnect) │ │ │
|
||||||
|
│ │ └──────────────────────────────────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ USB Serial │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ NanoVNA-H │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connection Lifecycle
|
||||||
|
|
||||||
|
1. **First tool call**: Auto-discovers USB device, opens serial at 115200 baud
|
||||||
|
2. **Idle < 30s**: Trusts existing connection
|
||||||
|
3. **Idle ≥ 30s**: Sends sync probe to validate; reconnects on failure
|
||||||
|
4. **Retry**: 2 attempts with 300ms delay on cold/stale ports
|
||||||
|
|
||||||
|
No manual connect/disconnect needed—the server manages the connection automatically.
|
||||||
114
src/content/docs/mcnanovna/prompts.mdx
Normal file
114
src/content/docs/mcnanovna/prompts.mdx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
title: Prompts
|
||||||
|
description: Guided workflow prompts for mcnanovna
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Prompts are pre-built conversation templates that guide the AI through multi-step procedures. They set up context and provide step-by-step instructions for common workflows.
|
||||||
|
|
||||||
|
## Available Prompts
|
||||||
|
|
||||||
|
### Calibration & Setup
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `calibrate` | Full SOLT calibration walkthrough |
|
||||||
|
| `export_touchstone` | Export S-parameters to .s1p/.s2p |
|
||||||
|
|
||||||
|
### Antenna Analysis
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `analyze_antenna` | SWR, impedance, bandwidth analysis |
|
||||||
|
| `visualize_radiation_pattern` | Generate 3D pattern from S11 |
|
||||||
|
| `measure_antenna_range` | Automated 3D pattern with positioner |
|
||||||
|
| `import_pattern` | Import external pattern files |
|
||||||
|
|
||||||
|
### Component Analysis
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `measure_component` | Identify unknown L/C/R |
|
||||||
|
| `analyze_crystal` | Crystal parameter extraction |
|
||||||
|
| `analyze_filter_response` | Filter characterization |
|
||||||
|
| `measure_lc_series` | Series LC resonator |
|
||||||
|
| `measure_lc_shunt` | Shunt LC resonator |
|
||||||
|
|
||||||
|
### Cable & Transmission Line
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `measure_cable` | Cable characterization |
|
||||||
|
| `measure_tdr` | Time domain reflectometry |
|
||||||
|
|
||||||
|
### Design
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `impedance_match` | L-network matching design |
|
||||||
|
| `compare_sweeps` | Before/after comparison |
|
||||||
|
|
||||||
|
## Prompt Parameters
|
||||||
|
|
||||||
|
Most prompts accept parameters to customize the workflow:
|
||||||
|
|
||||||
|
### calibrate
|
||||||
|
|
||||||
|
```
|
||||||
|
calibrate(
|
||||||
|
band="2m", # Ham band name or "custom"
|
||||||
|
start_hz=144000000, # Start frequency (overrides band)
|
||||||
|
stop_hz=148000000, # Stop frequency (overrides band)
|
||||||
|
points=101, # Sweep points
|
||||||
|
save_slot=0 # Flash slot for calibration
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### analyze_antenna
|
||||||
|
|
||||||
|
```
|
||||||
|
analyze_antenna(
|
||||||
|
band="2m", # Ham band name
|
||||||
|
start_hz=None, # Override start frequency
|
||||||
|
stop_hz=None, # Override stop frequency
|
||||||
|
points=101 # Sweep points
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### visualize_radiation_pattern
|
||||||
|
|
||||||
|
```
|
||||||
|
visualize_radiation_pattern(
|
||||||
|
antenna_type="dipole", # dipole, monopole, efhw, loop, patch, auto
|
||||||
|
band="2m",
|
||||||
|
points=101
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Use `band="custom"` with explicit `start_hz` and `stop_hz` for frequencies outside the ham band presets.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Ham Band Presets
|
||||||
|
|
||||||
|
| Band | Frequency Range |
|
||||||
|
|------|-----------------|
|
||||||
|
| 160m | 1.8 - 2.0 MHz |
|
||||||
|
| 80m | 3.5 - 4.0 MHz |
|
||||||
|
| 60m | 5.33 - 5.40 MHz |
|
||||||
|
| 40m | 7.0 - 7.3 MHz |
|
||||||
|
| 30m | 10.1 - 10.15 MHz |
|
||||||
|
| 20m | 14.0 - 14.35 MHz |
|
||||||
|
| 17m | 18.07 - 18.17 MHz |
|
||||||
|
| 15m | 21.0 - 21.45 MHz |
|
||||||
|
| 12m | 24.89 - 24.99 MHz |
|
||||||
|
| 10m | 28.0 - 29.7 MHz |
|
||||||
|
| 6m | 50 - 54 MHz |
|
||||||
|
| 2m | 144 - 148 MHz |
|
||||||
|
| 70cm | 420 - 450 MHz |
|
||||||
|
| 23cm | 1.24 - 1.30 GHz |
|
||||||
|
| full | 50 kHz - 900 MHz |
|
||||||
|
| hf | 1.8 - 30 MHz |
|
||||||
|
| vhf | 50 - 300 MHz |
|
||||||
|
| uhf | 300 MHz - 1 GHz |
|
||||||
159
src/content/docs/mcnanovna/tools.mdx
Normal file
159
src/content/docs/mcnanovna/tools.mdx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
title: Tool Reference
|
||||||
|
description: All 78 mcnanovna MCP tools
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Measurement Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `info` | Get device info (firmware, serial, frequency range) |
|
||||||
|
| `sweep` | Set sweep range (start, stop, points) |
|
||||||
|
| `scan` | Run measurement scan, return S-parameter data |
|
||||||
|
| `data` | Get current trace data without new measurement |
|
||||||
|
| `frequencies` | Get frequency list for current sweep |
|
||||||
|
| `marker` | Set/read marker position and values |
|
||||||
|
| `cal` | Run calibration step (open, short, load, thru, isoln, done) |
|
||||||
|
| `save` | Save calibration to flash slot |
|
||||||
|
| `recall` | Recall calibration from flash slot |
|
||||||
|
| `pause` | Pause continuous sweep |
|
||||||
|
| `resume` | Resume continuous sweep |
|
||||||
|
|
||||||
|
## Configuration Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `power` | Set/get output power level |
|
||||||
|
| `bandwidth` | Set/get IF bandwidth |
|
||||||
|
| `edelay` | Set/get electrical delay compensation |
|
||||||
|
| `s21offset` | Set/get S21 offset correction |
|
||||||
|
| `vbat` | Read battery voltage |
|
||||||
|
| `capture` | Capture LCD screenshot (PNG) |
|
||||||
|
| `measure` | Set measurement mode |
|
||||||
|
| `config` | Get full device configuration |
|
||||||
|
| `saveconfig` | Save configuration to flash |
|
||||||
|
| `clearconfig` | Reset configuration to defaults |
|
||||||
|
| `color` | Set/get display colors |
|
||||||
|
| `freq` | Set single frequency (CW mode) |
|
||||||
|
| `tcxo` | Set/get TCXO calibration |
|
||||||
|
| `vbat_offset` | Set/get battery voltage offset |
|
||||||
|
| `threshold` | Set/get measurement thresholds |
|
||||||
|
|
||||||
|
## Display Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `trace` | Configure trace display settings |
|
||||||
|
| `transform` | Enable/configure time domain transform |
|
||||||
|
| `smooth` | Set trace smoothing |
|
||||||
|
| `touchcal` | Run touchscreen calibration |
|
||||||
|
| `touchtest` | Test touchscreen |
|
||||||
|
| `refresh` | Force display refresh |
|
||||||
|
| `touch` | Simulate touch press |
|
||||||
|
| `release` | Simulate touch release |
|
||||||
|
|
||||||
|
## Device Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `reset` | Reset device |
|
||||||
|
| `version` | Get firmware version |
|
||||||
|
| `detect` | Detect and connect to VNA |
|
||||||
|
| `disconnect` | Close serial connection |
|
||||||
|
| `raw_command` | Send raw command to device |
|
||||||
|
| `cw` | Set continuous wave output |
|
||||||
|
| `sd_list` | List SD card files |
|
||||||
|
| `sd_read` | Read file from SD card |
|
||||||
|
| `sd_delete` | Delete file from SD card |
|
||||||
|
| `time` | Get/set device time |
|
||||||
|
|
||||||
|
## Diagnostics Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `i2c` | I2C bus diagnostics |
|
||||||
|
| `si` | Si5351 clock chip status |
|
||||||
|
| `lcd` | LCD controller info |
|
||||||
|
| `threads` | FreeRTOS thread status |
|
||||||
|
| `stat` | System statistics |
|
||||||
|
| `sample` | Raw ADC samples |
|
||||||
|
| `test` | Run self-test |
|
||||||
|
| `gain` | Set/get receiver gain |
|
||||||
|
| `dump` | Memory dump |
|
||||||
|
| `port` | Port configuration |
|
||||||
|
| `offset` | Calibration offsets |
|
||||||
|
| `dac` | DAC control |
|
||||||
|
| `usart_cfg` | USART configuration |
|
||||||
|
| `usart` | USART I/O |
|
||||||
|
| `band` | Band settings |
|
||||||
|
|
||||||
|
## Analysis Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `analyze` | Full scan analysis (SWR, Z, return loss) |
|
||||||
|
| `export_touchstone` | Export to .s1p/.s2p format |
|
||||||
|
| `export_csv` | Export to CSV format |
|
||||||
|
| `analyze_filter` | Characterize filter response |
|
||||||
|
| `analyze_xtal` | Extract crystal parameters |
|
||||||
|
| `analyze_tdr` | Time domain reflectometry |
|
||||||
|
| `analyze_component` | Identify unknown L/C/R |
|
||||||
|
| `analyze_lc_series` | Measure series LC resonator |
|
||||||
|
| `analyze_lc_shunt` | Measure shunt LC resonator |
|
||||||
|
| `analyze_lc_match` | Design L-network matching |
|
||||||
|
| `analyze_s11_resonance` | Find S11 resonance points |
|
||||||
|
|
||||||
|
## Radiation Pattern Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `radiation_pattern` | Generate 3D pattern from S11 scan |
|
||||||
|
| `radiation_pattern_from_data` | Generate pattern from known impedance |
|
||||||
|
| `radiation_pattern_multi` | Multi-frequency pattern comparison |
|
||||||
|
|
||||||
|
## Pattern Import Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `import_pattern_csv` | Import from CSV file |
|
||||||
|
| `import_pattern_emcar` | Import from EMCAR vna.dat |
|
||||||
|
| `import_pattern_nec2` | Import from NEC2 output |
|
||||||
|
| `import_pattern_s1p` | Import from Touchstone S1P |
|
||||||
|
| `list_pattern_formats` | List supported import formats |
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Basic Scan">
|
||||||
|
```
|
||||||
|
User: Scan my antenna from 144 to 148 MHz with 201 points
|
||||||
|
|
||||||
|
Claude uses: sweep(144000000, 148000000, 201)
|
||||||
|
Claude uses: scan(s11=true)
|
||||||
|
Claude uses: analyze()
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Filter Analysis">
|
||||||
|
```
|
||||||
|
User: Analyze this bandpass filter from 1 to 500 MHz
|
||||||
|
|
||||||
|
Claude uses: sweep(1000000, 500000000, 201)
|
||||||
|
Claude uses: analyze_filter()
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Calibration">
|
||||||
|
```
|
||||||
|
User: Calibrate for the 2m band
|
||||||
|
|
||||||
|
Claude uses: sweep(144000000, 148000000, 101)
|
||||||
|
Claude uses: cal("load") # user connects load
|
||||||
|
Claude uses: cal("open") # user connects open
|
||||||
|
Claude uses: cal("short") # user connects short
|
||||||
|
Claude uses: cal("thru") # user connects through
|
||||||
|
Claude uses: cal("done")
|
||||||
|
Claude uses: save(0)
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
137
src/content/docs/mcnanovna/webui.mdx
Normal file
137
src/content/docs/mcnanovna/webui.mdx
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
title: Web UI
|
||||||
|
description: 3D radiation pattern viewer
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
mcnanovna includes an optional Three.js-based 3D viewer for antenna radiation patterns.
|
||||||
|
|
||||||
|
## Enabling the Web UI
|
||||||
|
|
||||||
|
Set the `MCNANOVNA_WEB_PORT` environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
MCNANOVNA_WEB_PORT=8080 uvx mcnanovna
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://localhost:8080 in your browser.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
The web UI requires optional dependencies. From source: `uv sync --extra webui`
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 3D Pattern Visualization
|
||||||
|
|
||||||
|
- Interactive rotation with mouse drag
|
||||||
|
- Zoom with scroll wheel
|
||||||
|
- Gain-mapped color gradient (red = high, blue = low)
|
||||||
|
- dBi reference rings
|
||||||
|
|
||||||
|
### Pattern Sources
|
||||||
|
|
||||||
|
1. **From VNA scan**: Run `radiation_pattern` tool
|
||||||
|
2. **From known impedance**: Use `radiation_pattern_from_data`
|
||||||
|
3. **File upload**: Load CSV, EMCAR, NEC2, or S1P files
|
||||||
|
|
||||||
|
### Controls
|
||||||
|
|
||||||
|
| Control | Action |
|
||||||
|
|---------|--------|
|
||||||
|
| Left drag | Rotate view |
|
||||||
|
| Scroll | Zoom in/out |
|
||||||
|
| Right drag | Pan |
|
||||||
|
| Double-click | Reset view |
|
||||||
|
|
||||||
|
### Side Panel
|
||||||
|
|
||||||
|
- Antenna type selector
|
||||||
|
- Frequency display
|
||||||
|
- Impedance readout
|
||||||
|
- Peak gain indicator
|
||||||
|
- Toggle reference rings
|
||||||
|
- Toggle axes
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
The web UI provides REST and WebSocket endpoints:
|
||||||
|
|
||||||
|
### REST
|
||||||
|
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/api/pattern/compute` | POST | Compute pattern from S11 data |
|
||||||
|
| `/api/pattern/current` | GET | Get current pattern data |
|
||||||
|
| `/api/status` | GET | Server status |
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
|
||||||
|
Connect to `/ws` for real-time pattern updates during measurement.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ws = new WebSocket('ws://localhost:8080/ws');
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
const pattern = JSON.parse(event.data);
|
||||||
|
// Update visualization
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern Format
|
||||||
|
|
||||||
|
All patterns use this JSON structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"antenna_type": "dipole",
|
||||||
|
"frequency_hz": 145000000,
|
||||||
|
"theta_deg": [0, 5, 10, ...],
|
||||||
|
"phi_deg": [0, 10, 20, ...],
|
||||||
|
"gain_dbi": [-40, -35, -20, ...],
|
||||||
|
"peak_gain_dbi": 2.15,
|
||||||
|
"num_points": 6552
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Upload
|
||||||
|
|
||||||
|
Click "Load File" to import external patterns:
|
||||||
|
|
||||||
|
| Format | Extension | Notes |
|
||||||
|
|--------|-----------|-------|
|
||||||
|
| CSV | .csv | 2 or 3 columns |
|
||||||
|
| EMCAR | .dat | Antenna range format |
|
||||||
|
| NEC2 | .out, .nec | Simulation output |
|
||||||
|
| S1P | .s1p | Touchstone → analytical |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Browser │
|
||||||
|
│ ┌───────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Three.js Scene │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │ │
|
||||||
|
│ │ │ Pattern Mesh│ │ OrbitControl│ │ Reference │ │ │
|
||||||
|
│ │ │(gain→color) │ │ (camera) │ │ Rings │ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └───────────┘ │ │
|
||||||
|
│ └───────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ HTTP/WebSocket │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌───────────────────────────────────────────────────┐ │
|
||||||
|
│ │ FastAPI (webui/api.py) │ │
|
||||||
|
│ │ Pattern compute, WebSocket push │ │
|
||||||
|
│ └───────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ Shared NanoVNA │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌───────────────────────────────────────────────────┐ │
|
||||||
|
│ │ mcnanovna MCP server │ │
|
||||||
|
│ │ (same process, asyncio.Lock) │ │
|
||||||
|
│ └───────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
The web UI runs in a daemon thread sharing the same process as the MCP server. An `asyncio.Lock` prevents concurrent VNA access from web and MCP.
|
||||||
89
src/content/docs/mcpositioner/overview.mdx
Normal file
89
src/content/docs/mcpositioner/overview.mdx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
title: mcpositioner Overview
|
||||||
|
description: MCP server for ESP32 dual-axis antenna positioner
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
mcpositioner gives AI assistants control of an ESP32-based dual-axis antenna positioner over WiFi. It drives two NEMA 17 stepper motors via TMC2209 drivers for theta (polar, 0-180°) and phi (azimuth, 0-360°) rotation.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Automated 3D antenna radiation pattern measurement. The positioner physically rotates an antenna under test through a theta/phi grid while a VNA measures transmission (S21) at each position.
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
- **Absolute positioning**: Move to any theta/phi coordinate
|
||||||
|
- **Relative moves**: Offset from current position
|
||||||
|
- **StallGuard homing**: Sensorless homing using TMC2209 stall detection
|
||||||
|
- **Emergency stop**: Immediate motor halt
|
||||||
|
- **Motion tuning**: Configurable speed, acceleration, microstepping
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
|
||||||
|
| Component | Specification |
|
||||||
|
|-----------|--------------|
|
||||||
|
| Controller | ESP32 DevKit (any variant) |
|
||||||
|
| Drivers | 2x TMC2209 in UART mode |
|
||||||
|
| Motors | 2x NEMA 17 (0.9° or 1.8° step) |
|
||||||
|
| Power | 24V for motors, 5V for ESP32 |
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Full build instructions and KiCad schematics are in the [Hardware section](/hardware/positioner-build/).
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Claude Code │
|
||||||
|
│ │ │
|
||||||
|
│ MCP Protocol │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────┐ │
|
||||||
|
│ │ mcpositioner server │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────────────┐ │ │
|
||||||
|
│ │ │ tools.py │ │ positioner.py │ │ │
|
||||||
|
│ │ │ 5 tools │─────▶│ httpx client │ │ │
|
||||||
|
│ │ └─────────────┘ └──────────┬──────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ WiFi HTTP │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────────────────────┐ │
|
||||||
|
│ │ ESP32 Firmware │ │
|
||||||
|
│ │ ┌─────────┐ ┌──────────────────┐ │ │
|
||||||
|
│ │ │AccelStep│ │ESPAsyncWebServer │ │ │
|
||||||
|
│ │ └────┬────┘ └────────┬─────────┘ │ │
|
||||||
|
│ │ │ HTTP API │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌─────────────────────────────┐ │ │
|
||||||
|
│ │ │ TMC2209 UART (StallGuard) │ │ │
|
||||||
|
│ │ └──────────┬──────────────────┘ │ │
|
||||||
|
│ └─────────────│────────────────────────┘ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────────────────────────────────┐ │
|
||||||
|
│ │ NEMA 17 Steppers (θ and φ axes) │ │
|
||||||
|
│ └──────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Discovery
|
||||||
|
|
||||||
|
The ESP32 firmware advertises itself via mDNS as `positioner.local`. Override with the `MCPOSITIONER_HOST` environment variable if your network doesn't support mDNS or you have multiple positioners.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use IP address instead of mDNS
|
||||||
|
MCPOSITIONER_HOST=192.168.1.100 uvx mcpositioner
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cross-Server Workflow
|
||||||
|
|
||||||
|
mcpositioner works with [mcnanovna](/mcnanovna/overview/) for automated pattern measurement:
|
||||||
|
|
||||||
|
1. **mcpositioner**: Move antenna to theta/phi position
|
||||||
|
2. **mcnanovna**: Measure S21 transmission
|
||||||
|
3. **Repeat** across the measurement grid
|
||||||
|
4. **Assemble** pattern from collected data
|
||||||
|
|
||||||
|
The `measure_pattern_grid` prompt guides this workflow step by step.
|
||||||
97
src/content/docs/mcpositioner/prompts.mdx
Normal file
97
src/content/docs/mcpositioner/prompts.mdx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
title: Prompts
|
||||||
|
description: Guided workflow prompts for mcpositioner
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Available Prompts
|
||||||
|
|
||||||
|
| Prompt | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `home_positioner` | Guided homing with safety checks |
|
||||||
|
| `configure_positioner` | Motion parameter tuning |
|
||||||
|
| `measure_pattern_grid` | Cross-server 3D pattern measurement |
|
||||||
|
|
||||||
|
## home_positioner
|
||||||
|
|
||||||
|
Guides through safe positioner homing with pre-flight checks.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `axis` | string | `"both"` | Which axis to home |
|
||||||
|
|
||||||
|
**What it covers:**
|
||||||
|
1. Verify positioner is reachable
|
||||||
|
2. Check for obstructions
|
||||||
|
3. Confirm cables have slack
|
||||||
|
4. Run StallGuard homing
|
||||||
|
5. Verify homed state
|
||||||
|
|
||||||
|
## configure_positioner
|
||||||
|
|
||||||
|
Helps tune motion parameters for your specific setup.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `speed` | float | None | Target speed (steps/sec) |
|
||||||
|
| `accel` | float | None | Target acceleration |
|
||||||
|
| `microstepping` | int | None | Microstep divisor |
|
||||||
|
|
||||||
|
**What it covers:**
|
||||||
|
- Parameter reference table
|
||||||
|
- Speed vs. measurement quality tradeoffs
|
||||||
|
- Microstepping guide
|
||||||
|
- Test move verification
|
||||||
|
|
||||||
|
## measure_pattern_grid
|
||||||
|
|
||||||
|
The key cross-server workflow for automated 3D antenna pattern measurement.
|
||||||
|
|
||||||
|
<Aside type="note">
|
||||||
|
This prompt requires both mcpositioner AND mcnanovna MCP servers to be running.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `antenna_type` | string | `"dipole"` | Label for metadata |
|
||||||
|
| `band` | string | `"2m"` | Ham band name |
|
||||||
|
| `theta_step` | float | `5.0` | Polar angle step (°) |
|
||||||
|
| `phi_step` | float | `10.0` | Azimuth step (°) |
|
||||||
|
| `points` | int | `51` | VNA frequency points |
|
||||||
|
| `settle_ms` | int | `200` | Settle time after move |
|
||||||
|
|
||||||
|
**What it covers:**
|
||||||
|
|
||||||
|
1. **Pre-flight checks** on both servers
|
||||||
|
2. **Homing** the positioner
|
||||||
|
3. **VNA calibration** reminder
|
||||||
|
4. **Grid measurement** procedure:
|
||||||
|
- Serpentine path optimization
|
||||||
|
- S21 extraction method
|
||||||
|
- Progress tracking
|
||||||
|
5. **Pattern assembly** format
|
||||||
|
6. **Resolution tradeoffs** table
|
||||||
|
|
||||||
|
**Grid examples:**
|
||||||
|
|
||||||
|
| Step Size | Grid Points | Est. Time | Use Case |
|
||||||
|
|-----------|------------|-----------|----------|
|
||||||
|
| 10° × 20° | 342 | ~9 min | Quick survey |
|
||||||
|
| 5° × 10° | 1332 | ~33 min | Standard |
|
||||||
|
| 2° × 5° | 6552 | ~164 min | High-resolution |
|
||||||
|
|
||||||
|
## Using Prompts
|
||||||
|
|
||||||
|
In Claude Code, invoke prompts by name:
|
||||||
|
|
||||||
|
```
|
||||||
|
User: Run the measure_pattern_grid prompt for my Yagi on 70cm
|
||||||
|
|
||||||
|
Claude: [Uses measure_pattern_grid prompt with antenna_type="yagi", band="70cm"]
|
||||||
|
```
|
||||||
|
|
||||||
|
The prompt provides step-by-step guidance, and Claude executes the tools from both mcpositioner and mcnanovna as needed.
|
||||||
151
src/content/docs/mcpositioner/tools.mdx
Normal file
151
src/content/docs/mcpositioner/tools.mdx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
title: Tool Reference
|
||||||
|
description: All 5 mcpositioner MCP tools
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `positioner_status` | Get current position and state |
|
||||||
|
| `positioner_move` | Move to absolute theta/phi position |
|
||||||
|
| `positioner_home` | Run sensorless homing |
|
||||||
|
| `positioner_stop` | Emergency stop all motors |
|
||||||
|
| `positioner_config` | Get/set motion parameters |
|
||||||
|
|
||||||
|
## positioner_status
|
||||||
|
|
||||||
|
Returns the current state of the positioner.
|
||||||
|
|
||||||
|
**Response fields:**
|
||||||
|
- `theta_deg`: Current polar angle (0-180°)
|
||||||
|
- `phi_deg`: Current azimuth angle (0-360°)
|
||||||
|
- `moving`: True if motors are currently moving
|
||||||
|
- `homed`: True if homing has been completed
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theta_deg": 45.0,
|
||||||
|
"phi_deg": 90.0,
|
||||||
|
"moving": false,
|
||||||
|
"homed": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## positioner_move
|
||||||
|
|
||||||
|
Move to an absolute theta/phi position.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `theta_deg` | float | required | Target polar angle (0-180°) |
|
||||||
|
| `phi_deg` | float | required | Target azimuth angle (0-360°) |
|
||||||
|
| `wait` | bool | `true` | Wait for move to complete |
|
||||||
|
| `poll_interval_ms` | int | `100` | Status polling interval |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
positioner_move(theta_deg=90, phi_deg=45, wait=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Always home the positioner before making moves. Without homing, the position reference is unknown.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## positioner_home
|
||||||
|
|
||||||
|
Run sensorless StallGuard homing on one or both axes.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `axis` | string | `"both"` | Which axis: `"theta"`, `"phi"`, or `"both"` |
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
1. Motors move slowly in the negative direction
|
||||||
|
2. TMC2209 detects stall when motor hits mechanical stop
|
||||||
|
3. Position counter is zeroed
|
||||||
|
4. Motor backs off slightly from the stop
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
positioner_home(axis="both")
|
||||||
|
```
|
||||||
|
|
||||||
|
## positioner_stop
|
||||||
|
|
||||||
|
Emergency stop all motors immediately.
|
||||||
|
|
||||||
|
**Parameters:** None
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
positioner_stop()
|
||||||
|
```
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
Use this if the antenna is about to collide with something or cables are getting tangled.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## positioner_config
|
||||||
|
|
||||||
|
Get or set motion parameters.
|
||||||
|
|
||||||
|
**Parameters (all optional):**
|
||||||
|
| Parameter | Type | Range | Description |
|
||||||
|
|-----------|------|-------|-------------|
|
||||||
|
| `speed` | float | 100-5000 | Max speed (steps/sec) |
|
||||||
|
| `accel` | float | 100-3000 | Acceleration (steps/sec²) |
|
||||||
|
| `microstepping` | int | 1-256 | Microstep divisor |
|
||||||
|
|
||||||
|
**Get current config:**
|
||||||
|
```
|
||||||
|
positioner_config()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Set parameters:**
|
||||||
|
```
|
||||||
|
positioner_config(speed=2000, accel=1000, microstepping=16)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tuning guide:**
|
||||||
|
|
||||||
|
| Use Case | Speed | Accel | Notes |
|
||||||
|
|----------|-------|-------|-------|
|
||||||
|
| Fast survey | 3000 | 2000 | More vibration, use longer settle time |
|
||||||
|
| Precision | 1000 | 500 | Smoother, less overshoot |
|
||||||
|
| Heavy antenna | 500 | 300 | Prevents missed steps |
|
||||||
|
|
||||||
|
## Example Workflow
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Basic Movement">
|
||||||
|
```
|
||||||
|
# Home first
|
||||||
|
positioner_home(axis="both")
|
||||||
|
|
||||||
|
# Move to measurement position
|
||||||
|
positioner_move(theta_deg=90, phi_deg=0, wait=true)
|
||||||
|
|
||||||
|
# Check position
|
||||||
|
positioner_status()
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Grid Measurement">
|
||||||
|
```
|
||||||
|
# Home and configure
|
||||||
|
positioner_home(axis="both")
|
||||||
|
positioner_config(speed=1500, accel=800)
|
||||||
|
|
||||||
|
# Measure at multiple points
|
||||||
|
for theta in [0, 30, 60, 90, 120, 150, 180]:
|
||||||
|
for phi in [0, 45, 90, 135, 180, 225, 270, 315]:
|
||||||
|
positioner_move(theta_deg=theta, phi_deg=phi, wait=true)
|
||||||
|
# ... run VNA measurement here ...
|
||||||
|
```
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
92
src/content/docs/reference/ham-bands.mdx
Normal file
92
src/content/docs/reference/ham-bands.mdx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
title: Ham Bands
|
||||||
|
description: Amateur radio frequency band presets
|
||||||
|
---
|
||||||
|
|
||||||
|
mcnanovna prompts accept ham band names as shortcuts for frequency ranges.
|
||||||
|
|
||||||
|
## Band Presets
|
||||||
|
|
||||||
|
### HF Bands
|
||||||
|
|
||||||
|
| Band | Start | Stop | Notes |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| 160m | 1.800 MHz | 2.000 MHz | Top Band |
|
||||||
|
| 80m | 3.500 MHz | 4.000 MHz | Night propagation |
|
||||||
|
| 60m | 5.3305 MHz | 5.4035 MHz | Channelized in many countries |
|
||||||
|
| 40m | 7.000 MHz | 7.300 MHz | Worldwide, day and night |
|
||||||
|
| 30m | 10.100 MHz | 10.150 MHz | WARC band, CW/data only |
|
||||||
|
| 20m | 14.000 MHz | 14.350 MHz | Primary DX band |
|
||||||
|
| 17m | 18.068 MHz | 18.168 MHz | WARC band |
|
||||||
|
| 15m | 21.000 MHz | 21.450 MHz | Good daytime DX |
|
||||||
|
| 12m | 24.890 MHz | 24.990 MHz | WARC band |
|
||||||
|
| 10m | 28.000 MHz | 29.700 MHz | Solar cycle dependent |
|
||||||
|
|
||||||
|
### VHF/UHF Bands
|
||||||
|
|
||||||
|
| Band | Start | Stop | Notes |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| 6m | 50 MHz | 54 MHz | "Magic Band" - sporadic E |
|
||||||
|
| 2m | 144 MHz | 148 MHz | Primary VHF band |
|
||||||
|
| 70cm | 420 MHz | 450 MHz | Primary UHF band |
|
||||||
|
| 23cm | 1.240 GHz | 1.300 GHz | Microwave |
|
||||||
|
|
||||||
|
### Aggregate Ranges
|
||||||
|
|
||||||
|
| Name | Start | Stop | Notes |
|
||||||
|
|------|-------|------|-------|
|
||||||
|
| full | 50 kHz | 900 MHz | NanoVNA-H full range |
|
||||||
|
| hf | 1.8 MHz | 30 MHz | All HF bands |
|
||||||
|
| vhf | 50 MHz | 300 MHz | VHF region |
|
||||||
|
| uhf | 300 MHz | 1 GHz | UHF region |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
In prompts:
|
||||||
|
|
||||||
|
```
|
||||||
|
calibrate(band="2m")
|
||||||
|
analyze_antenna(band="20m")
|
||||||
|
measure_antenna_range(band="70cm")
|
||||||
|
```
|
||||||
|
|
||||||
|
Or override with explicit frequencies:
|
||||||
|
|
||||||
|
```
|
||||||
|
calibrate(start_hz=144000000, stop_hz=148000000)
|
||||||
|
```
|
||||||
|
|
||||||
|
## NanoVNA Frequency Limits
|
||||||
|
|
||||||
|
| Model | Min | Max |
|
||||||
|
|-------|-----|-----|
|
||||||
|
| NanoVNA-H | 50 kHz | 900 MHz |
|
||||||
|
| NanoVNA-H4 | 50 kHz | 1.5 GHz |
|
||||||
|
|
||||||
|
Bands outside these ranges can't be measured directly.
|
||||||
|
|
||||||
|
## Harmonic Measurements
|
||||||
|
|
||||||
|
For bands above the VNA's range, harmonics can sometimes be used:
|
||||||
|
|
||||||
|
- 23cm (1.2 GHz) on NanoVNA-H: Measure at 1/3 frequency (400 MHz) and look for harmonic response
|
||||||
|
|
||||||
|
This is imprecise but can give a rough idea of antenna behavior.
|
||||||
|
|
||||||
|
## Band Plans
|
||||||
|
|
||||||
|
These presets cover the full amateur allocation. Actual band plans vary by country and license class. Consult your local regulations for permitted frequencies.
|
||||||
|
|
||||||
|
### US Band Plan Example (2m)
|
||||||
|
|
||||||
|
| Segment | Start | Stop | Use |
|
||||||
|
|---------|-------|------|-----|
|
||||||
|
| CW/SSB | 144.000 | 144.100 | Weak signal |
|
||||||
|
| SSB | 144.100 | 144.275 | SSB calling |
|
||||||
|
| Beacons | 144.275 | 144.300 | Propagation beacons |
|
||||||
|
| FM simplex | 146.400 | 146.580 | FM simplex |
|
||||||
|
| Repeater inputs | 144.600 | 144.900 | Linked repeaters |
|
||||||
|
| Repeater outputs | 145.200 | 145.500 | Linked repeaters |
|
||||||
|
| FM calling | 146.520 | 146.520 | National calling |
|
||||||
|
|
||||||
|
For antenna analysis, measure across the full band allocation to see behavior at all frequencies you might use.
|
||||||
201
src/content/docs/reference/http-api.mdx
Normal file
201
src/content/docs/reference/http-api.mdx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
title: HTTP API (ESP32)
|
||||||
|
description: ESP32 positioner firmware HTTP endpoints
|
||||||
|
---
|
||||||
|
|
||||||
|
The ESP32 positioner firmware exposes a REST API for position control.
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
Default: `http://positioner.local`
|
||||||
|
|
||||||
|
Override with `MCPOSITIONER_HOST` environment variable.
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### GET /status
|
||||||
|
|
||||||
|
Returns current positioner state.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theta_deg": 45.0,
|
||||||
|
"phi_deg": 90.0,
|
||||||
|
"moving": false,
|
||||||
|
"homed": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `theta_deg` | float | Current polar angle (0-180°) |
|
||||||
|
| `phi_deg` | float | Current azimuth angle (0-360°) |
|
||||||
|
| `moving` | bool | True if motors are moving |
|
||||||
|
| `homed` | bool | True if homing completed |
|
||||||
|
|
||||||
|
### POST /move
|
||||||
|
|
||||||
|
Move to absolute position.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theta_deg": 90,
|
||||||
|
"phi_deg": 45
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "moving",
|
||||||
|
"target_theta": 90,
|
||||||
|
"target_phi": 45
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /move/relative
|
||||||
|
|
||||||
|
Move relative to current position.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"d_theta": 5,
|
||||||
|
"d_phi": -10
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "moving",
|
||||||
|
"target_theta": 95,
|
||||||
|
"target_phi": 80
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /home
|
||||||
|
|
||||||
|
Run sensorless homing.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"axis": "both"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Valid values for `axis`: `"theta"`, `"phi"`, `"both"`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "homing",
|
||||||
|
"axis": "both"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /stop
|
||||||
|
|
||||||
|
Emergency stop all motors.
|
||||||
|
|
||||||
|
**Request:** (empty body)
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "stopped"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /config
|
||||||
|
|
||||||
|
Get current motion parameters.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"speed": 2000,
|
||||||
|
"accel": 1000,
|
||||||
|
"microstepping": 16,
|
||||||
|
"theta_steps_per_deg": 17.78,
|
||||||
|
"phi_steps_per_deg": 17.78
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### POST /config
|
||||||
|
|
||||||
|
Update motion parameters.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"speed": 1500,
|
||||||
|
"accel": 800
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only include fields you want to change.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"speed": 1500,
|
||||||
|
"accel": 800,
|
||||||
|
"microstepping": 16
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Errors return HTTP 4xx/5xx with JSON body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "description of the error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Common errors:
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 400 | Invalid request (bad JSON, out of range) |
|
||||||
|
| 503 | Busy (already moving/homing) |
|
||||||
|
|
||||||
|
## Example: curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Get status
|
||||||
|
curl http://positioner.local/status
|
||||||
|
|
||||||
|
# Move to position
|
||||||
|
curl -X POST http://positioner.local/move \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"theta_deg": 90, "phi_deg": 0}'
|
||||||
|
|
||||||
|
# Home both axes
|
||||||
|
curl -X POST http://positioner.local/home \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"axis": "both"}'
|
||||||
|
|
||||||
|
# Emergency stop
|
||||||
|
curl -X POST http://positioner.local/stop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example: Python
|
||||||
|
|
||||||
|
```python
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
async def move_positioner(theta: float, phi: float):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(
|
||||||
|
"http://positioner.local/move",
|
||||||
|
json={"theta_deg": theta, "phi_deg": phi}
|
||||||
|
)
|
||||||
|
return response.json()
|
||||||
|
```
|
||||||
163
src/content/docs/reference/pattern-formats.mdx
Normal file
163
src/content/docs/reference/pattern-formats.mdx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
---
|
||||||
|
title: Pattern Formats
|
||||||
|
description: Supported antenna pattern file formats
|
||||||
|
---
|
||||||
|
|
||||||
|
mcnanovna can import antenna patterns from several file formats.
|
||||||
|
|
||||||
|
## CSV
|
||||||
|
|
||||||
|
Comma-separated values with angle and gain data.
|
||||||
|
|
||||||
|
### 3-Column Format (Full 3D)
|
||||||
|
|
||||||
|
```csv
|
||||||
|
theta,phi,gain_dbi
|
||||||
|
0,0,-40
|
||||||
|
0,10,-40
|
||||||
|
...
|
||||||
|
90,0,2.15
|
||||||
|
90,10,2.15
|
||||||
|
...
|
||||||
|
180,0,-40
|
||||||
|
```
|
||||||
|
|
||||||
|
| Column | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| theta | Polar angle (0-180°) |
|
||||||
|
| phi | Azimuth angle (0-360°) |
|
||||||
|
| gain_dbi | Gain in dBi |
|
||||||
|
|
||||||
|
### 2-Column Format (Single Cut)
|
||||||
|
|
||||||
|
```csv
|
||||||
|
angle,gain
|
||||||
|
0,-40
|
||||||
|
45,-10
|
||||||
|
90,2.15
|
||||||
|
135,-10
|
||||||
|
180,-40
|
||||||
|
```
|
||||||
|
|
||||||
|
Single-cut data is synthesized to 3D using a sin(θ) taper.
|
||||||
|
|
||||||
|
### Delimiters
|
||||||
|
|
||||||
|
Auto-detected: comma (`,`), semicolon (`;`), or tab.
|
||||||
|
|
||||||
|
### Headers
|
||||||
|
|
||||||
|
Optional. If first row contains non-numeric values, it's treated as a header.
|
||||||
|
|
||||||
|
## EMCAR vna.dat
|
||||||
|
|
||||||
|
Format from EMCAR antenna range software.
|
||||||
|
|
||||||
|
```
|
||||||
|
# EMCAR antenna pattern measurement
|
||||||
|
# Frequency: 145 MHz
|
||||||
|
0 0.5
|
||||||
|
45 0.8
|
||||||
|
90 1.0
|
||||||
|
135 0.8
|
||||||
|
180 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
| Column | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| angle | Azimuth angle (degrees) |
|
||||||
|
| amplitude | Linear amplitude (0-1) |
|
||||||
|
|
||||||
|
The gnuplot transform is applied automatically:
|
||||||
|
- Angle: `(-angle + 90)` rotation
|
||||||
|
- Gain: `20 * log10(amplitude + 0.01)`
|
||||||
|
|
||||||
|
## NEC2 Output
|
||||||
|
|
||||||
|
Output from NEC2/NEC4 antenna simulation.
|
||||||
|
|
||||||
|
```
|
||||||
|
- - - RADIATION PATTERNS - - -
|
||||||
|
|
||||||
|
- ANGLES - - POWER GAINS - - POLARIZATION -
|
||||||
|
THETA PHI VERT. HOR. TOTAL AXIAL TILT
|
||||||
|
DEGREES DEGREES DB DB DB RATIO DEG
|
||||||
|
0.00 0.00 -999.99 -999.99 -999.99 0.00000 0.00
|
||||||
|
5.00 0.00 -4.52 -999.99 -4.52 0.00000 90.00
|
||||||
|
10.00 0.00 1.43 -999.99 1.43 0.00000 90.00
|
||||||
|
```
|
||||||
|
|
||||||
|
### Polarization Selection
|
||||||
|
|
||||||
|
| Option | Column |
|
||||||
|
|--------|--------|
|
||||||
|
| `total` | TOTAL (default) |
|
||||||
|
| `vert` | VERT. |
|
||||||
|
| `hor` | HOR. |
|
||||||
|
|
||||||
|
The frequency is auto-detected from the NEC2 header.
|
||||||
|
|
||||||
|
## Touchstone S1P
|
||||||
|
|
||||||
|
Touchstone S-parameter file with S11 data.
|
||||||
|
|
||||||
|
```
|
||||||
|
! NanoVNA measurement
|
||||||
|
# Hz S RI R 50
|
||||||
|
144000000 0.15 -0.08
|
||||||
|
145000000 0.02 -0.01
|
||||||
|
146000000 0.12 0.05
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formats
|
||||||
|
|
||||||
|
| Option | Data Format |
|
||||||
|
|--------|-------------|
|
||||||
|
| RI | Real, Imaginary |
|
||||||
|
| MA | Magnitude, Angle (degrees) |
|
||||||
|
| DB | dB Magnitude, Angle (degrees) |
|
||||||
|
|
||||||
|
### Processing
|
||||||
|
|
||||||
|
1. Find resonance (minimum |S11|)
|
||||||
|
2. Calculate impedance at resonance
|
||||||
|
3. Estimate antenna type from impedance
|
||||||
|
4. Generate analytical pattern
|
||||||
|
|
||||||
|
### Antenna Type Override
|
||||||
|
|
||||||
|
If auto-detection is wrong, specify the type:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import_pattern_s1p(content, antenna_type="monopole")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Internal Format
|
||||||
|
|
||||||
|
All import functions return the same structure:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"antenna_type": "dipole",
|
||||||
|
"frequency_hz": 145000000,
|
||||||
|
"theta_deg": [0, 5, 10, ...], # List of theta values
|
||||||
|
"phi_deg": [0, 10, 20, ...], # List of phi values
|
||||||
|
"gain_dbi": [-40, -35, ...], # Gain at each point
|
||||||
|
"peak_gain_dbi": 2.15,
|
||||||
|
"num_points": 6552
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This format is used by:
|
||||||
|
- Web UI visualization
|
||||||
|
- `radiation_pattern_multi` comparison
|
||||||
|
- Export functions
|
||||||
|
|
||||||
|
## Interpolation
|
||||||
|
|
||||||
|
For irregular measurement grids, IDW (inverse-distance-weighted) interpolation maps data to the standard theta×phi grid:
|
||||||
|
|
||||||
|
- Theta: 0° to 180° in 5° steps (default)
|
||||||
|
- Phi: 0° to 355° in 5° steps (default)
|
||||||
|
|
||||||
|
Great-circle distance is used for accurate angular weighting.
|
||||||
134
src/content/docs/tutorials/antenna-analysis.mdx
Normal file
134
src/content/docs/tutorials/antenna-analysis.mdx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
title: Antenna Analysis
|
||||||
|
description: Using mcnanovna to analyze antenna performance
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Tabs, TabItem, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
This tutorial covers common antenna analysis tasks using mcnanovna.
|
||||||
|
|
||||||
|
## Basic SWR and Impedance
|
||||||
|
|
||||||
|
The most common antenna measurement is SWR (Standing Wave Ratio) and impedance at the feedpoint.
|
||||||
|
|
||||||
|
Ask Claude: "Analyze my antenna from 144 to 148 MHz"
|
||||||
|
|
||||||
|
Claude will:
|
||||||
|
1. Run a sweep across the band
|
||||||
|
2. Find the resonant frequency (minimum SWR)
|
||||||
|
3. Calculate impedance at resonance
|
||||||
|
4. Report bandwidth where SWR < 2:1
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
Resonant frequency: 145.2 MHz
|
||||||
|
SWR at resonance: 1.15:1
|
||||||
|
Impedance at resonance: 48.5 + j2.3 Ω
|
||||||
|
Bandwidth (SWR < 2:1): 143.8 - 146.9 MHz (3.1 MHz)
|
||||||
|
Return loss at resonance: -23.4 dB
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finding Resonance
|
||||||
|
|
||||||
|
For antennas with multiple resonances (like a multi-band antenna):
|
||||||
|
|
||||||
|
Ask Claude: "Find all resonances from 1 to 30 MHz"
|
||||||
|
|
||||||
|
Claude uses `analyze_s11_resonance` to find all points where the antenna is resonant.
|
||||||
|
|
||||||
|
## Impedance Matching
|
||||||
|
|
||||||
|
If your antenna doesn't match 50Ω, design a matching network:
|
||||||
|
|
||||||
|
Ask Claude: "Design a matching network for 35+j25 ohms at 145 MHz"
|
||||||
|
|
||||||
|
Claude uses `analyze_lc_match` to compute L-network solutions:
|
||||||
|
```
|
||||||
|
Solution 1: Series L (27 nH) + Shunt C (18 pF)
|
||||||
|
Solution 2: Shunt C (12 pF) + Series L (42 nH)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Antenna Types
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<TabItem label="Dipole">
|
||||||
|
**Expected characteristics:**
|
||||||
|
- Resonant impedance: ~73Ω
|
||||||
|
- Narrow bandwidth
|
||||||
|
- Figure-8 radiation pattern
|
||||||
|
|
||||||
|
Ask Claude: "Analyze my dipole on 20m"
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Vertical">
|
||||||
|
**Expected characteristics:**
|
||||||
|
- Resonant impedance: ~36Ω (ground-mounted)
|
||||||
|
- Needs matching network or radials
|
||||||
|
- Omnidirectional pattern
|
||||||
|
|
||||||
|
Ask Claude: "Analyze my vertical on 40m"
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Yagi">
|
||||||
|
**Expected characteristics:**
|
||||||
|
- Low impedance at feedpoint (~25Ω)
|
||||||
|
- Narrow bandwidth
|
||||||
|
- Directional pattern
|
||||||
|
|
||||||
|
Ask Claude: "Analyze my Yagi from 144 to 148 MHz"
|
||||||
|
</TabItem>
|
||||||
|
<TabItem label="Loop">
|
||||||
|
**Expected characteristics:**
|
||||||
|
- Variable impedance based on size
|
||||||
|
- High Q (narrow bandwidth)
|
||||||
|
- Figure-8 pattern (small loop)
|
||||||
|
|
||||||
|
Ask Claude: "Analyze my magnetic loop on 40m"
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
## Troubleshooting Antennas
|
||||||
|
|
||||||
|
### High SWR everywhere
|
||||||
|
|
||||||
|
Possible causes:
|
||||||
|
- Feedline not connected properly
|
||||||
|
- Antenna not resonant in measurement range
|
||||||
|
- Major construction problem
|
||||||
|
|
||||||
|
What to check:
|
||||||
|
- Verify feedline continuity
|
||||||
|
- Widen the measurement range
|
||||||
|
- Check physical dimensions
|
||||||
|
|
||||||
|
### SWR dip but wrong frequency
|
||||||
|
|
||||||
|
The antenna is resonant but not where you want it.
|
||||||
|
|
||||||
|
- **Too low**: Antenna is electrically long → shorten
|
||||||
|
- **Too high**: Antenna is electrically short → lengthen
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
For a dipole, each side is approximately λ/4 in length. At 145 MHz, that's about 51 cm per side.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
### Good SWR but high reactance
|
||||||
|
|
||||||
|
The antenna is resonant but not at 50Ω.
|
||||||
|
|
||||||
|
Ask Claude: "Design a matching network for my measured impedance"
|
||||||
|
|
||||||
|
### Narrow bandwidth
|
||||||
|
|
||||||
|
High-Q antennas (small loops, loaded verticals) have narrow bandwidth. Options:
|
||||||
|
- Accept it (tune for the portion of band you use)
|
||||||
|
- Add loading to lower Q
|
||||||
|
- Use an antenna tuner
|
||||||
|
|
||||||
|
## Radiation Patterns
|
||||||
|
|
||||||
|
For a quick analytical pattern based on antenna type:
|
||||||
|
|
||||||
|
Ask Claude: "Show the radiation pattern for my dipole"
|
||||||
|
|
||||||
|
This uses the S11 data to determine resonance and impedance, then generates an idealized 3D pattern.
|
||||||
|
|
||||||
|
For measured patterns, see [3D Pattern Measurement](/tutorials/pattern-measurement/).
|
||||||
138
src/content/docs/tutorials/calibration.mdx
Normal file
138
src/content/docs/tutorials/calibration.mdx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
title: VNA Calibration
|
||||||
|
description: SOLT calibration for accurate measurements
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
Calibration removes systematic errors from VNA measurements. Without calibration, your readings will include errors from cables, adapters, and the VNA itself.
|
||||||
|
|
||||||
|
## When to Calibrate
|
||||||
|
|
||||||
|
- First use on a new frequency range
|
||||||
|
- After changing cables or adapters
|
||||||
|
- After significant temperature changes
|
||||||
|
- If measurements look wrong or inconsistent
|
||||||
|
|
||||||
|
## What You Need
|
||||||
|
|
||||||
|
### Calibration Standards
|
||||||
|
|
||||||
|
| Standard | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| **50Ω Load** | Reference impedance |
|
||||||
|
| **Open** | Open circuit (can use bare SMA) |
|
||||||
|
| **Short** | Short circuit |
|
||||||
|
| **Through** | Connects Port 1 to Port 2 |
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
An SMA calibration kit includes precision standards. For casual use, a 50Ω terminator, SMA barrel, and bare connectors work reasonably well.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Calibration Procedure
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. **Set the sweep range**
|
||||||
|
|
||||||
|
Ask Claude: "Set sweep from 144 to 148 MHz with 101 points"
|
||||||
|
|
||||||
|
Calibration is frequency-specific. Always calibrate at the frequencies you'll measure.
|
||||||
|
|
||||||
|
2. **Connect the LOAD**
|
||||||
|
|
||||||
|
Connect the 50Ω load termination to Port 1.
|
||||||
|
|
||||||
|
Ask Claude: "Run cal load"
|
||||||
|
|
||||||
|
3. **Connect the OPEN**
|
||||||
|
|
||||||
|
Remove the load, leave Port 1 open (or use an open standard).
|
||||||
|
|
||||||
|
Ask Claude: "Run cal open"
|
||||||
|
|
||||||
|
4. **Connect the SHORT**
|
||||||
|
|
||||||
|
Connect the short standard to Port 1.
|
||||||
|
|
||||||
|
Ask Claude: "Run cal short"
|
||||||
|
|
||||||
|
5. **Connect the THROUGH**
|
||||||
|
|
||||||
|
Connect Port 1 to Port 2 with a short cable or barrel adapter.
|
||||||
|
|
||||||
|
Ask Claude: "Run cal thru"
|
||||||
|
|
||||||
|
6. **Optional: Isolation**
|
||||||
|
|
||||||
|
Connect 50Ω loads to both ports.
|
||||||
|
|
||||||
|
Ask Claude: "Run cal isoln"
|
||||||
|
|
||||||
|
This corrects for crosstalk between ports.
|
||||||
|
|
||||||
|
7. **Apply calibration**
|
||||||
|
|
||||||
|
Ask Claude: "Run cal done"
|
||||||
|
|
||||||
|
This computes and applies the error correction coefficients.
|
||||||
|
|
||||||
|
8. **Save calibration**
|
||||||
|
|
||||||
|
Ask Claude: "Save calibration to slot 0"
|
||||||
|
|
||||||
|
Saves to NanoVNA flash for later recall.
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After calibration, verify it worked:
|
||||||
|
|
||||||
|
1. **Load test**: Connect 50Ω load. S11 should be < -40 dB.
|
||||||
|
2. **Open test**: Leave port open. S11 should be ~0 dB.
|
||||||
|
3. **Through test**: Connect through. S21 should be ~0 dB, S11 < -40 dB.
|
||||||
|
|
||||||
|
## Calibration Slots
|
||||||
|
|
||||||
|
The NanoVNA stores calibrations in flash memory:
|
||||||
|
|
||||||
|
| Model | Slots |
|
||||||
|
|-------|-------|
|
||||||
|
| NanoVNA-H | 0-4 |
|
||||||
|
| NanoVNA-H4 | 0-6 |
|
||||||
|
|
||||||
|
Each slot holds one calibration. Common approach:
|
||||||
|
- Slot 0: Full range (50 kHz - 900 MHz)
|
||||||
|
- Slot 1: HF (1.8 - 30 MHz)
|
||||||
|
- Slot 2: VHF (50 - 300 MHz)
|
||||||
|
- Slot 3: 2m band (144 - 148 MHz)
|
||||||
|
- Slot 4: 70cm band (420 - 450 MHz)
|
||||||
|
|
||||||
|
## Recalling Calibration
|
||||||
|
|
||||||
|
Ask Claude: "Recall calibration from slot 0"
|
||||||
|
|
||||||
|
The NanoVNA will use the stored calibration. Note that calibration must match the current sweep settings.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### High return loss with load
|
||||||
|
|
||||||
|
- Clean connectors with isopropyl alcohol
|
||||||
|
- Check for damaged center pin
|
||||||
|
- Try a different load
|
||||||
|
|
||||||
|
### S21 shows high loss through
|
||||||
|
|
||||||
|
- Cable may be damaged
|
||||||
|
- Adapters may be loose
|
||||||
|
- Recalibrate with the exact cables you'll use
|
||||||
|
|
||||||
|
### Calibration doesn't apply
|
||||||
|
|
||||||
|
- Frequency range must match
|
||||||
|
- Try recalibrating
|
||||||
|
- Check firmware version
|
||||||
|
|
||||||
|
<Aside type="caution">
|
||||||
|
Calibration is only valid for the exact cables and adapters used during calibration. If you change anything in the signal path, recalibrate.
|
||||||
|
</Aside>
|
||||||
138
src/content/docs/tutorials/pattern-measurement.mdx
Normal file
138
src/content/docs/tutorials/pattern-measurement.mdx
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
title: 3D Pattern Measurement
|
||||||
|
description: Automated antenna radiation pattern measurement with positioner and VNA
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Steps, Aside } from '@astrojs/starlight/components';
|
||||||
|
|
||||||
|
This tutorial walks through measuring a complete 3D antenna radiation pattern using mcpositioner and mcnanovna together.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- NanoVNA-H connected via USB
|
||||||
|
- ESP32 positioner built and on WiFi
|
||||||
|
- Both MCP servers added to Claude Code:
|
||||||
|
```bash
|
||||||
|
claude mcp add mcnanovna -- uvx mcnanovna
|
||||||
|
claude mcp add mcpositioner -- uvx mcpositioner
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Physical Arrangement
|
||||||
|
|
||||||
|
```
|
||||||
|
Transmit Antenna
|
||||||
|
(on VNA Port 1)
|
||||||
|
│
|
||||||
|
│ Line of sight
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
│ Positioner │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
│ │ Antenna │ │
|
||||||
|
│ │ Under Test │◄── VNA Port 2
|
||||||
|
│ │ (rotates) │ │
|
||||||
|
│ └────────┬────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ θ axis │
|
||||||
|
│ │ │
|
||||||
|
│ ──────────────── │
|
||||||
|
│ φ axis │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Transmit antenna**: Fixed, connected to VNA Port 1
|
||||||
|
- **Antenna under test**: Mounted on positioner, connected to VNA Port 2
|
||||||
|
- **Distance**: Far-field (at least 2λ, preferably 10λ+)
|
||||||
|
|
||||||
|
<Aside type="tip">
|
||||||
|
For accurate patterns, the transmit antenna should have a known, broad pattern (like a dipole) so it illuminates the AUT evenly across all angles.
|
||||||
|
</Aside>
|
||||||
|
|
||||||
|
## Measurement Procedure
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. **Verify both servers are connected**
|
||||||
|
|
||||||
|
Ask Claude: "Check positioner status and VNA info"
|
||||||
|
|
||||||
|
Claude will call `positioner_status` and `info` to verify both devices respond.
|
||||||
|
|
||||||
|
2. **Home the positioner**
|
||||||
|
|
||||||
|
Ask Claude: "Home the positioner on both axes"
|
||||||
|
|
||||||
|
This establishes the reference position (theta=0, phi=0).
|
||||||
|
|
||||||
|
3. **Calibrate the VNA**
|
||||||
|
|
||||||
|
Ask Claude: "Calibrate for the 2m band"
|
||||||
|
|
||||||
|
Follow the SOLT calibration procedure. This is critical for accurate S21 measurements.
|
||||||
|
|
||||||
|
4. **Run the pattern measurement**
|
||||||
|
|
||||||
|
Ask Claude: "Measure a 3D radiation pattern for my dipole antenna on 2m"
|
||||||
|
|
||||||
|
Claude uses the `measure_pattern_grid` prompt to guide the measurement:
|
||||||
|
- Confirms grid resolution (default: 5° theta × 10° phi)
|
||||||
|
- Moves through each grid point in serpentine order
|
||||||
|
- Measures S21 at each position
|
||||||
|
- Assembles the pattern data
|
||||||
|
|
||||||
|
5. **View the results**
|
||||||
|
|
||||||
|
If the mcnanovna web UI is running (`MCNANOVNA_WEB_PORT=8080`), the pattern renders in 3D automatically.
|
||||||
|
|
||||||
|
Or ask Claude: "Show the pattern statistics"
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Resolution Tradeoffs
|
||||||
|
|
||||||
|
| Step Size | Grid Points | Time | Use Case |
|
||||||
|
|-----------|------------|------|----------|
|
||||||
|
| 10° × 20° | 342 | ~9 min | Quick survey, verify setup |
|
||||||
|
| 5° × 10° | 1332 | ~33 min | Standard measurement |
|
||||||
|
| 2° × 5° | 6552 | ~2.7 hr | Publication-quality |
|
||||||
|
|
||||||
|
## Pattern Normalization
|
||||||
|
|
||||||
|
By default, patterns are **relative**—the peak is set to 0 dBi. This shows the pattern shape but not absolute gain.
|
||||||
|
|
||||||
|
For **absolute gain**, measure a reference antenna first:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
1. Mount a known-gain antenna (e.g., calibrated dipole at 2.15 dBi)
|
||||||
|
2. Measure S21 at bore-sight
|
||||||
|
3. Note the reference S21 value
|
||||||
|
4. Measure your antenna under test
|
||||||
|
5. Offset all measurements: `gain_dBi = S21_dB - reference_S21_dB + reference_gain_dBi`
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Pattern looks wrong
|
||||||
|
|
||||||
|
- Check that the transmit antenna is properly aimed
|
||||||
|
- Verify far-field distance (reflections cause errors)
|
||||||
|
- Recalibrate VNA if temperature changed significantly
|
||||||
|
|
||||||
|
### Measurement is noisy
|
||||||
|
|
||||||
|
- Increase VNA averaging (use more points)
|
||||||
|
- Reduce IF bandwidth
|
||||||
|
- Check cable connections
|
||||||
|
|
||||||
|
### Positioner loses position
|
||||||
|
|
||||||
|
- Motors may be missing steps—reduce speed
|
||||||
|
- Check mechanical coupling for slippage
|
||||||
|
- Increase motor current in firmware
|
||||||
|
|
||||||
|
### Web UI not updating
|
||||||
|
|
||||||
|
- Check `MCNANOVNA_WEB_PORT` is set
|
||||||
|
- Verify browser can reach the port
|
||||||
|
- Check for WebSocket connection errors in browser console
|
||||||
57
src/styles/custom.css
Normal file
57
src/styles/custom.css
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/* Custom styles for mcnanovna docs */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sl-color-accent-low: #1a365d;
|
||||||
|
--sl-color-accent: #2b6cb0;
|
||||||
|
--sl-color-accent-high: #4299e1;
|
||||||
|
--sl-color-white: #fff;
|
||||||
|
--sl-color-gray-1: #edf2f7;
|
||||||
|
--sl-color-gray-2: #e2e8f0;
|
||||||
|
--sl-color-gray-3: #cbd5e0;
|
||||||
|
--sl-color-gray-4: #a0aec0;
|
||||||
|
--sl-color-gray-5: #718096;
|
||||||
|
--sl-color-gray-6: #4a5568;
|
||||||
|
--sl-color-black: #1a202c;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme='dark'] {
|
||||||
|
--sl-color-accent-low: #1e3a5f;
|
||||||
|
--sl-color-accent: #3182ce;
|
||||||
|
--sl-color-accent-high: #63b3ed;
|
||||||
|
--sl-color-white: #1a202c;
|
||||||
|
--sl-color-gray-1: #2d3748;
|
||||||
|
--sl-color-gray-2: #4a5568;
|
||||||
|
--sl-color-gray-3: #718096;
|
||||||
|
--sl-color-gray-4: #a0aec0;
|
||||||
|
--sl-color-gray-5: #cbd5e0;
|
||||||
|
--sl-color-gray-6: #e2e8f0;
|
||||||
|
--sl-color-black: #f7fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tool reference tables */
|
||||||
|
.tool-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-table th,
|
||||||
|
.tool-table td {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--sl-color-gray-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-table code {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background: var(--sl-color-gray-1);
|
||||||
|
padding: 0.125rem 0.375rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hardware diagrams */
|
||||||
|
.wiring-diagram {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid var(--sl-color-gray-3);
|
||||||
|
}
|
||||||
9
tsconfig.json
Normal file
9
tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user