Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,28 @@ Open the printed local URL (Vite default is `http://localhost:5173`).
3. Start streaming data - the app supports CSV, space, or tab-separated values

### Data Format
Your device should send lines of numeric data:
Your device should send lines of numeric data with optional bracket-based filtering:
```text
# Optional header defines series names
# Standard format (works with filters disabled)
# time(ms), ax, ay, az
0, 0.01, 0.02, 0.98
10, 0.02, 0.01, 0.99
20, 0.03, 0.00, 1.01

# Bracket format (recommended - filters enabled by default)
Debug: Sensor initialized
[# Temperature,Humidity,Pressure]
[22.45,65.23,1015.67]
Debug: Reading sensor...
[22.67,64.89,1015.23]
[23.01,65.45,1016.12]
```

**Bracket Filtering (Enabled by Default):**
- Lines enclosed in `[brackets]` go to the chart
- Lines without brackets appear only in the console
- Perfect for mixing debug messages with sensor data
- Toggle filters in Settings panel if needed

### Interactive Controls
- **Pan**: Click and drag on the plot
- **Zoom**: Ctrl+wheel or pinch on touch devices
Expand Down Expand Up @@ -163,6 +176,21 @@ The Console tab provides direct device communication:
- **Export Options**: Save console logs as TXT, CSV, or JSON files
- **Configurable Buffer**: Adjust message history size (10-10,000 messages)

### Settings Profiles
Save and restore your preferred configurations:
- **Default Profile**: Standard settings for new sessions
- **Custom Profile**: Appears when you modify Default settings
- **Save Profile**: Create named profiles from Custom settings
- **Overwrite/Save As**: Update existing profiles or create new ones
- Profiles persist in browser storage across sessions

### Data Filters
Control which data appears where:
- **Chart Filter**: Only plot lines enclosed in `[brackets]` (ON by default)
- **Console Filter**: Hide `[bracketed]` lines from console (ON by default)
- Use both to separate chart data from debug messages
- Disable both for legacy behavior (all data goes everywhere)

## Browser Support

The Web Serial API is supported in modern Chromium‑based browsers:
Expand Down
35 changes: 27 additions & 8 deletions example_firmware/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ A basic example that generates simulated sensor data with 4 series:
- Light (lux)

### Features
- Outputs data at 10 Hz (100ms intervals)
- Outputs data at 50 Hz (20ms intervals)
- Uses sine waves with random noise for realistic sensor simulation
- Demonstrates bracket-based filtering with mixed debug/data output
- Includes proper header line with series names
- Compatible with standard Arduino boards (Uno, Nano, ESP32, etc.)

Expand All @@ -27,18 +28,34 @@ A basic example that generates simulated sensor data with 4 series:

### Data Format

The sketch outputs data in the format expected by the Web Serial Plotter:
The sketch outputs data using the bracket-based filtering format:

```
# Temperature,Humidity,Pressure,Light
22.45,65.23,1015.67,789.12
22.67,64.89,1015.23,792.45
Chart header:
[# Temperature,Humidity,Pressure,Light]
Chart values:
[22.45,65.23,1015.67,789.12]
[22.67,64.89,1015.23,792.45]
[23.01,65.45,1016.12,791.23]
...
```

- Header line starts with `#` followed by comma-separated series names
- Data lines contain comma-separated numerical values
- Each line represents one time sample across all series
**Bracket Filtering (Enabled by Default):**
- Lines enclosed in `[brackets]` are sent to the chart
- Lines without brackets are shown only in the console
- This allows mixing debug messages with sensor data on the same serial connection

**Format Rules:**
- Header line: `[# series1,series2,...]` with `#` prefix and comma-separated names
- Data lines: `[value1,value2,...]` with comma-separated numerical values
- Debug/status messages: plain text without brackets (console only)
- Each bracketed line represents one time sample across all series

**Filter Settings:**
You can toggle the filters in the Settings panel:
- **Chart filter**: When ON (default), only `[bracketed]` lines are plotted
- **Console filter**: When ON (default), `[bracketed]` lines are hidden from console
- Turn both OFF to use the plotter without filtering (legacy behavior)

### Customization

Expand All @@ -47,6 +64,8 @@ You can modify the sketch to:
- Adjust sampling rate (modify `delay()` value)
- Change data generation functions
- Add real sensor readings instead of simulated data
- Add or remove debug messages
- Use without brackets for legacy behavior (disable filters in settings)

### Serial Settings

Expand Down
19 changes: 15 additions & 4 deletions example_firmware/basic_plotter/basic_plotter.ino
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* Compatible with Web Serial Plotter at:
* https://github.com/your-repo/web-serial-plotter
*/
long lastDebug = 0;
int debugCount = 0;

void setup() {
Serial.begin(115200);
Expand All @@ -21,28 +23,37 @@ void setup() {
delay(10);
}

Serial.println("# Temperature,Humidity,Pressure,Light");
Serial.println("[# Temperature,Humidity,Pressure,Light]");

// Small delay to ensure header is processed
delay(100);
}

void loop() {
// Send debug info every 1 second
if(millis() - lastDebug > 1000){
Serial.print("Debug no. ");
Serial.println(++debugCount);
lastDebug = millis();
}

// Generate sample sensor data
float temperature = 20.0 + 15.0 * sin(millis() / 5000.0) + random(-100, 100) / 100.0;
float humidity = 50.0 + 20.0 * cos(millis() / 3000.0) + random(-200, 200) / 100.0;
float pressure = 1013.25 + 10.0 * sin(millis() / 8000.0) + random(-50, 50) / 100.0;
float light = 500.0 + 300.0 * sin(millis() / 2000.0) + random(-1000, 1000) / 100.0;

// Output as comma-separated values
Serial.print("[");
Serial.print(temperature, 2);
Serial.print(",");
Serial.print(humidity, 2);
Serial.print(",");
Serial.print(pressure, 2);
Serial.print(",");
Serial.println(light, 2);
Serial.print(light, 2);
Serial.println("]");

// Sample rate: 10 Hz (100ms interval)
delay(100);
// Sample rate: 50 Hz (20ms interval)
delay(20);
}
37 changes: 30 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ function App() {
const [timeMode, setTimeMode] = useState<'absolute' | 'relative'>('absolute')
const [activeTab, setActiveTab] = useState<'chart' | 'console'>('chart')
const [showSettingsPanel, setShowSettingsPanel] = useState(false)
const [filterChartData, setFilterChartData] = useState(true)
const [filterConsoleData, setFilterConsoleData] = useState(true)
const tourSteps = useMemo(() => ([
{
element: '#tour-connect-button',
Expand Down Expand Up @@ -68,24 +70,41 @@ function App() {
const handleIncomingLine = useCallback((line: string) => {
setLastLine(line)

// Send to console store (always log all incoming data)
consoleStore.addIncoming(line)
const trimmedLine = line.trim()
const isBracketed = trimmedLine.startsWith('[') && trimmedLine.endsWith(']')

// Parse for chart (existing logic)
if (line.trim().startsWith('#')) {
const names = line.replace(/^\(/, '').replace(/\)$/, '').replace(/^\s*#+\s*/, '').split(/[\s,\t]+/).filter(Boolean)
// Determine if line should go to console
const sendToConsole = !filterConsoleData || !isBracketed
if (sendToConsole) {
consoleStore.addIncoming(line)
}

// Determine if line should go to chart
const sendToChart = !filterChartData || isBracketed
if (!sendToChart) return

// Extract content (remove brackets if present)
let content = trimmedLine
if (isBracketed) {
content = trimmedLine.slice(1, -1).trim()
}

// Parse for chart
if (content.startsWith('#')) {
const names = content.replace(/^\s*#+\s*/, '').split(/[\s,\t]+/).filter(Boolean)
if (names.length > 0) store.setSeries(names)
return
}
const parts = line.trim().replace(/^\(/, '').replace(/\)$/, '').split(/[\s,\t]+/).filter(Boolean)

const parts = content.split(/[\s,\t]+/).filter(Boolean)
if (parts.length === 0) return
const values: number[] = []
for (const p of parts) {
const v = Number(p)
if (Number.isFinite(v)) values.push(v)
}
if (values.length > 0) store.append(values)
}, [store, consoleStore])
}, [store, consoleStore, filterChartData, filterConsoleData])

const dataConnection = useDataConnection(handleIncomingLine)

Expand Down Expand Up @@ -263,6 +282,8 @@ function App() {
capacity: store.getCapacity(),
maxViewPortSize: store.getMaxViewPortSize(),
timeMode,
filterChartData,
filterConsoleData,
}}
onChange={{
setAutoscale,
Expand All @@ -271,6 +292,8 @@ function App() {
setCapacity: (v) => store.setCapacity(v),
setMaxViewPortSize: (v) => store.setMaxViewPortSize(v),
setTimeMode,
setFilterChartData,
setFilterConsoleData,
}}
onClose={() => setShowSettingsPanel(false)}
/>
Expand Down
Loading