A complete Point-of-Sale system with dual experiences (Grocery and Café), 3D floor simulators, an MCP server for agentic LLM integration, and an Agent Chat powered by Hermes Gateway with real-time table tracking via MQTT.
Switch between experiences from the toolbar toggle. Each experience has its own database, products, simulation model, and visual layout.
| Feature | Grocery | Café |
|---|---|---|
| Layout | 60×50 supermarket (produce, bakery, dairy, meat, aisles, 4 checkout lanes) | 30×30 café (L-shaped counter, espresso bar, pastry case, seating, window bar) |
| Customer flow | Wander → shop aisles → checkout → leave | Enter → queue → order → wait for drink → sit (dine-in) or leave (takeout) |
| Customer types | Shoppers | 60% takeout (orange) / 40% dine-in (purple) |
| Products | 20 grocery items across 8 categories | 32 items: espresso, coffee, tea, cold drinks, bagels, donuts, pastries, cakes |
| Customer range | 3–100 | 3–30 |
| Staff | — | Baristas (behind counter) + Floor staff (1:10 ratio, clears dirty tables) |
- Angular framework with Material Design
- Dark/Standard theme switching
- Experience switcher (Grocery / Café)
- Integrated simulator snap-on option
- Configuration page for MCP server management
- Agent Chat tab for agentic AI interaction
- Node.js + Express
- Dual PostgreSQL databases (one per experience)
- Experience-aware routing via
X-Experienceheader - Product and transaction management
- MQTT Table-Status Service — real-time table state tracking
- Chat Route — proxies to Hermes Gateway with SSE streaming
- Angular + Three.js (WebGL)
- Experience-specific scene (grocery or café)
- Configurable customer count and simulation speed (0.25x–5x)
- Interactive: orbit camera, click to select fixtures, move/rotate/scale (G/R/S keys)
- Zone labels and dollar sign effect on transactions
- Simulated vision events — emits table state changes (occupied/dirty/cleared) to backend
- pos-tools — 11 tools for querying products, sales, customers, and schema
- table-status — 9 tools for real-time table monitoring via MQTT vision events
- Toggle on/off from the Config UI or run standalone
- Chat UI with streaming responses
- Sidebar showing connection status, available tools, and live table grid
- Hermes Gateway connects to multiple MCP servers (POS, table-status, external vision AI)
- Simulator generates synthetic vision events when external hardware is unavailable
┌─────────────────────────────────────────────────────────────────┐
│ Frontend (Angular) │
│ POS │ Transactions │ Simulator │ Agent Chat │ Config │
└──────────────────────────────────────────────────┬──────────────┘
│ HTTP/SSE
┌──────────────────────────────────────────────────┼──────────────┐
│ Backend (Express) │ │
│ REST API │ MQTT Service │ Chat Proxy ───────────┘ │
│ │ │
│ MCP Servers: pos-tools │ table-status │
└────────────────┼────────────────────────────────────────────────┘
│ MQTT │ stdio
┌────────────────┼────────┐ ┌──────────┴──────────┐
│ MQTT Broker │ │ Hermes Gateway │
│ (Mosquitto) │ │ (LLM + MCP client) │
└────────────────┬─────────┘ └──────────────────────┘
│
┌────────────────┴─────────┐
│ Vision AI System │
│ (or Simulator fallback) │
└───────────────────────────┘
- Node.js 18+
- PostgreSQL 14+
- Angular CLI (
npm install -g @angular/cli) - MQTT Broker (optional, for table tracking — see below)
- Hermes Gateway (optional, for agent chat — see below)
# Grocery database
createdb -U postgres pos_grocery
psql -U postgres -d pos_grocery -f database/grocery/schema.sql
# Café database
createdb -U postgres pos_cafe
psql -U postgres -d pos_cafe -f database/cafe/schema.sqlcd backend
npm install
cp .env.example .env # Configure your DB connections
npm run devcd frontend
npm install
ng serveNavigate to http://localhost:4200
The table-status service subscribes to MQTT topics to receive vision events. Any MQTT v3.1.1/v5 broker works. Eclipse Mosquitto is recommended.
Windows (winget):
winget install EclipseMosquitto.MosquittomacOS:
brew install mosquitto
brew services start mosquittoLinux (apt):
sudo apt install -y mosquitto mosquitto-clients
sudo systemctl enable --now mosquittoDocker:
docker run -d --name mosquitto -p 1883:1883 eclipse-mosquitto:2# In one terminal, subscribe:
mosquitto_sub -t "cafe/vision/events"
# In another, publish a test event:
mosquitto_pub -t "cafe/vision/events" -m '{"tableId":"table-3","event":"dirty","source":"test","timestamp":"2026-01-01T00:00:00Z"}'Add to backend/.env:
MQTT_BROKER_URL=mqtt://localhost:1883
CAFE_TABLE_COUNT=15| Topic | Publisher | Description |
|---|---|---|
cafe/vision/events |
Vision AI / Simulator | Table state detections (occupied, dirty) |
cafe/pos/events |
POS Backend | Internal state changes |
cafe/staff/events |
Staff App | Manual table clearing |
{
"tableId": "table-4",
"event": "occupied|dirty|cleared",
"timestamp": "2026-05-19T10:30:00Z",
"source": "camera-2|simulator-vision|staff-app",
"metadata": {}
}Note: When no MQTT broker is available, the table-status service runs in offline mode. The café simulator still sends events directly via HTTP to
/api/chat/tables/event.
The Agent Chat tab communicates with Hermes Agent (v0.14+), an autonomous AI agent by Nous Research that connects to MCP servers and runs an LLM reasoning loop. It exposes a gateway API that the backend proxies to.
Linux / macOS / WSL2:
curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash
source ~/.bashrcWindows (native PowerShell — early beta):
iex (irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1)Via pip:
pip install hermes-agent
hermes postinstallRun the interactive model selector:
hermes modelSelect "Custom Endpoint" for local models (LM Studio, Ollama, vLLM, etc.) or choose a hosted provider (OpenRouter, Anthropic, OpenAI, Nous Portal, etc.).
Important: Hermes requires a model with at least 64K tokens of context. If using a local model, set context length to at least 65536.
Add the EdgeMart MCP servers to ~/.hermes/config.yaml:
mcp_servers:
edgemart-pos:
command: node
args:
- /path/to/pos-simulator/backend/src/mcp-server.js
env:
DB_HOST: localhost
DB_PORT: "5432"
MCP_DB_NAME: pos_cafe
DB_USER: postgres
DB_PASSWORD: ""
edgemart-table-status:
command: node
args:
- /path/to/pos-simulator/backend/src/mcp-table-status.js
env:
MQTT_BROKER_URL: mqtt://localhost:1883
CAFE_TABLE_COUNT: "15"Replace /path/to/pos-simulator with your actual project path.
Or set via CLI:
hermes config set mcp_servers.edgemart-pos.command node
hermes config set mcp_servers.edgemart-pos.args '["/path/to/pos-simulator/backend/src/mcp-server.js"]'The gateway includes an API Server that exposes Hermes as an OpenAI-compatible HTTP endpoint with full tool access. This is what the POS backend connects to.
Add to ~/.hermes/.env:
API_SERVER_ENABLED=true
API_SERVER_PORT=3001
API_SERVER_HOST=0.0.0.0
API_SERVER_KEY=change-me-local-devNote:
API_SERVER_HOST=0.0.0.0is required when running the POS stack in Docker so the backend container can reach Hermes on the host via the Docker bridge network. Use127.0.0.1only if running everything natively without Docker.
The API server runs as part of the gateway process:
# In the foreground (recommended for WSL/Docker/Termux):
hermes gateway run
# As a systemd/launchd service (Linux/macOS):
hermes gateway startYou should see:
[API Server] API server listening on http://127.0.0.1:3001
WSL users: Use
hermes gateway run— WSL's systemd support is unreliable. Wrap in tmux for persistence:tmux new -s hermes 'hermes gateway run'
**1. Follow the README in this repo to setup model server.
2. Configure Hermes to use LM Studio:
hermes model
# Select: "Custom Endpoint"
# Base URL: http://localhost:1234/v1
# API Key: lm-studio (any non-empty string)
# Model: hermes-3-llama-3.1-8b (as shown in LM Studio)Or set manually in ~/.hermes/config.yaml:
model: <your-model>
provider: lmstudio
base_url: http://localhost:8000/v33. Verify OVMS is serving:
curl http://localhost:8000/v3/models
# Should list the loaded model4. Start the gateway (API server starts automatically):
hermes gateway runTips for local models:
- EdgeMart does not recommend or endorse any specific model — choose one that fits your hardware and use case
- Use at least Q4_K_M quantization for reliable function calling
- Models with native tool-use training work best
- Ensure enough VRAM for model + 32K context
- If tool calls fail, try a larger model or higher quantization
Add to backend/.env:
HERMES_GATEWAY_URL=http://localhost:3001
HERMES_API_KEY=change-me-local-devThe backend proxies to the API server's OpenAI-compatible endpoints:
POST /v1/chat/completions— streaming chat with tool useGET /health— health checkGET /v1/models— list available models
hermes doctor # Check overall health
hermes gateway status # Check gateway is running
# Test the API server directly:
curl http://localhost:3001/health
# → {"status":"ok"}
curl http://localhost:3001/v1/models \
-H "Authorization: Bearer <your-api-key>"
# → lists "hermes-agent" model| Command | Purpose |
|---|---|
hermes setup |
Full setup wizard |
hermes model |
Choose/switch LLM provider |
hermes mcp add |
Add an MCP server |
hermes tools |
Configure enabled tools |
hermes gateway run |
Start gateway + API server in foreground |
hermes gateway start |
Start as background service (systemd/launchd) |
hermes gateway status |
Check gateway health |
hermes doctor |
Diagnose issues |
hermes update |
Update to latest version |
Once running, the Agent tab in the frontend will show a green connection indicator and you can chat with the agent.
The MCP server exposes POS data to agentic LLMs via the Model Context Protocol. By default it connects to the grocery database. Set MCP_DB_NAME env variable to target a specific database.
cd backend
npm run mcp # uses grocery DB
MCP_DB_NAME=pos_cafe npm run mcp # uses café DB| Tool | Description |
|---|---|
list_categories |
List all product categories |
search_products |
Search products by name or category |
get_product_details |
Get details for a specific product |
get_sales_summary |
Revenue summary with top products and category breakdown |
get_sales_by_date |
Daily sales totals for trend analysis |
get_product_sales |
Full sales history for a product |
list_customers |
List customers, optionally loyalty-only |
get_customer_purchases |
Purchase history for a customer |
get_top_customers |
Top customers by spending |
run_query |
Run ad-hoc read-only SQL queries |
get_schema |
View the database schema |
Exposes real-time table tracking to the Hermes agent.
cd backend
npm run mcp:tables| Tool | Description |
|---|---|
get_all_tables |
Current status of all café tables |
get_dirty_tables |
List tables needing cleaning |
get_occupied_tables |
List tables currently in use |
get_clean_tables |
List available tables |
get_table_history |
State change timeline for a specific table |
get_table_stats |
Aggregate stats (avg dirty duration, busiest, etc) |
mark_table_cleared |
Mark a table as cleared by staff |
mark_table_occupied |
Mark a table as occupied |
get_service_status |
MQTT connection and service health |
Add to your MCP client config (e.g. Claude Desktop claude_desktop_config.json):
{
"mcpServers": {
"edgemart-grocery": {
"command": "node",
"args": ["<path-to>/backend/src/mcp-server.js"]
},
"edgemart-table-status": {
"command": "node",
"args": ["<path-to>/backend/src/mcp-table-status.js"]
}
}
}When the external vision AI system is not available, the café simulator generates synthetic vision events:
| Simulator Event | MQTT Equivalent | Timing |
|---|---|---|
| Customer sits at table | occupied |
Immediate |
| Customer leaves table | dirty |
1–4s delay (simulates CV processing) |
| Staff clears table | cleared |
Immediate |
These events are POSTed to POST /api/chat/tables/event and feed into the TableStatusService, exercising the full pipeline without hardware.
Run the entire stack (frontend, backend, PostgreSQL, MQTT, GPU metrics) in containers with a single command.
- Docker Engine 24+ and Docker Compose v2
docker compose up --build| Service | Container | Port | Description |
|---|---|---|---|
frontend |
pos-frontend | 80 | Angular UI + nginx reverse proxy |
backend |
pos-backend | 3000 | Express API + metrics SSE |
postgres |
pos-postgres | 5432 | Dual databases (pos_grocery, pos_cafe) |
mqtt |
pos-mqtt | 1883, 9001 | MQTT broker (TCP + WebSocket) |
qmmd |
pos-qmmd | 9101 | Intel GPU metrics (Prometheus endpoint) |
Open http://localhost after startup.
Configure via .env in the project root (or export variables):
DB_PASSWORD=posdev2024
CAFE_TABLE_COUNT=15
HERMES_API_KEY=change-me-local-devdocker compose up --build backend frontenddocker compose down # Stop containers (keeps data volumes)
docker compose down -v # Stop and remove data volumes (full reset)On first run, PostgreSQL automatically:
- Creates
pos_groceryandpos_cafedatabases - Runs the schema + seed data from
database/grocery/schema.sqlanddatabase/cafe/schema.sql
To re-initialize, remove the postgres volume:
docker compose down -v
docker compose up --buildIf running Hermes outside Docker, point it at the exposed ports:
# ~/.hermes/config.yaml
mcp_servers:
edgemart-pos:
command: node
args: ["/path/to/pos-simulator/backend/src/mcp-server.js"]
env:
DB_HOST: localhost
DB_PORT: "5432"
DB_PASSWORD: "posdev2024"
MCP_DB_NAME: pos_cafe
edgemart-table-status:
command: node
args: ["/path/to/pos-simulator/backend/src/mcp-table-status.js"]
env:
MQTT_BROKER_URL: "mqtt://localhost:1883"
CAFE_TABLE_COUNT: "15"The qmmd container runs qmmd (Prometheus daemon from the qmassa project) with privileged: true, host PID namespace, and host networking. It exposes Intel GPU metrics on port 9101 which the backend scrapes every second.
The backend's /api/metrics/stream SSE endpoint collects:
- CPU/Memory — natively via Node.js
osmodule - GPU — scraped from qmmd at
http://host.docker.internal:9101/metrics - NPU — read from sysfs when available
The system metrics panel uses qmmd (a Prometheus daemon from the qmassa project) to read Intel GPU engine utilization, frequency, power, and temperature.
In Docker, qmmd runs as a dedicated container with host networking and PID namespace. For local development you can run it manually.
From crates.io (requires Rust toolchain):
# Install Rust if not already present
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Install qmmd
cargo install qmmdRun:
sudo qmmd -f -p 9101 -m 1000
# -f = use DRM fdinfo (no perf PMU needed)
# -p = port
# -m = update interval in msVerify:
curl http://localhost:9101/metrics- Linux with Intel GPU (i915 or xe driver)
- Root access (or appropriate capabilities)
- Access to
/dev/dri/,/sys/, and/proc/
| Metric | Description |
|---|---|
qmmd_gpu_engine_utilization_ratio |
Per-engine utilization (render, compute, video, copy, video-enhance) |
qmmd_gpu_actual_frequency_hertz |
Current GPU frequency |
qmmd_gpu_power_watts |
GPU and package power draw |
qmmd_gpu_temperature_celsius |
Package temperature |
Note: If qmmd is not reachable, the metrics panel gracefully hides the GPU section — all other metrics (CPU, NPU, memory) continue to work.
| Variable | Default | Description |
|---|---|---|
DB_HOST |
localhost |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_USER |
postgres |
PostgreSQL user |
DB_PASSWORD |
— | PostgreSQL password |
GROCERY_DB_NAME |
pos_grocery |
Grocery database name |
CAFE_DB_NAME |
pos_cafe |
Café database name |
MCP_DB_NAME |
(uses GROCERY_DB_NAME) | MCP server target DB |
MQTT_BROKER_URL |
mqtt://localhost:1883 |
MQTT broker connection string |
CAFE_TABLE_COUNT |
15 |
Number of tracked tables (9 floor + 6 bar) |
HERMES_GATEWAY_URL |
http://localhost:3001 |
Hermes agent gateway URL |
QMMD_URL |
http://qmmd:9101 |
qmmd GPU metrics endpoint |
PORT |
3000 |
Backend API port |
Or toggle the MCP server from the Config tab in the UI.