Skip to content
Merged
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
4 changes: 4 additions & 0 deletions docs/concepts/earthquake-early-warning.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Earthquake Early Warning
---

import {WarningTimeDiagram} from '@site/src/components/Diagrams';

# Earthquake Early Warning

Earthquake early warning (EEW) detects an earthquake after it begins and attempts to notify locations before damaging shaking reaches them. It is not earthquake prediction.
Expand Down Expand Up @@ -30,6 +32,8 @@ Every step consumes time. Warning time depends on the earthquake, station geomet

Locations close to the source can experience strong shaking before the system has enough observations to issue a useful alert. No network density or communications technology eliminates this blind zone completely.

<WarningTimeDiagram />

## Initial estimates change

The earliest data represent only the beginning of a rupture. Automated estimates can change as:
Expand Down
4 changes: 4 additions & 0 deletions docs/concepts/iot-approach.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Connected Sensor Networks
---

import {DataPathDiagram} from '@site/src/components/Diagrams';

# Connected Sensor Networks

Connected sensing combines compact instruments, standard communications, remote health telemetry, and centralized processing. It can make dense deployments practical, but connectivity does not replace good instrumentation or network design.
Expand Down Expand Up @@ -85,6 +87,8 @@ A connected station is an operational system. Plan for:

## Grillo architecture

<DataPathDiagram />

- **Grillo Pulse and Grillo One** send health reports and seismic data in real time over Ethernet, Wi-Fi, or cellular, initiating every connection outbound.
- **Grillo Cloud** manages projects, claimed sensors, station metadata, device health, and over-the-air firmware updates. Its [SISTEM add-on](/events) adds waveforms and processes seismic data into earthquake events.
- **Seismic data** can go to Grillo or, per project, [to your own server](/dashboard/data/data-server), while health monitoring stays in Grillo Cloud.
Expand Down
4 changes: 4 additions & 0 deletions docs/concepts/landslide-monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: Landslide Monitoring with GNSS
---

import {LandslideKitDiagram} from '@site/src/components/Diagrams';

# Landslide Monitoring with GNSS

Many landslides move slowly, by millimetres or centimetres over days to months, before they move fast. Measuring that slow movement shows which ground is active, how fast it is going, and whether it is speeding up.
Expand All @@ -14,6 +16,8 @@ A phone or a basic GPS receiver knows its position to within a few metres. Lands

**RTK** (real-time kinematic) positioning removes most of that error. A **base** receiver at a known, stable point watches the same satellites as a **rover** on the moving ground. Errors from the atmosphere and the satellites affect both receivers in almost the same way, so the base sends corrections and the rover cancels them out. Over short distances this gets the rover's position relative to the base down to centimetres or better.

<LandslideKitDiagram />

When the rover has used the corrections to resolve its position precisely, it reports an **RTK Fixed** solution. Grillo Slide only uses RTK Fixed readings to measure movement.

## Movement is measured against the base
Expand Down
117 changes: 117 additions & 0 deletions src/components/Diagrams/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type {ReactNode} from 'react';
import styles from './styles.module.css';

function Figure({title, caption, children}: {title: string; caption: string; children: ReactNode}): ReactNode {
return (
<figure className={styles.figure}>
<svg className={styles.svg} viewBox="0 0 720 360" role="img" aria-label={title}>
<title>{title}</title>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0L10 5L0 10z" className={styles.arrowHead} />
</marker>
<marker id="arrowAccent" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M0 0L10 5L0 10z" className={styles.accentFill} />
</marker>
</defs>
{children}
</svg>
<figcaption className={styles.caption}>{caption}</figcaption>
</figure>
);
}

function Device({x, y, label, sub, accent}: {x: number; y: number; label: string; sub?: string; accent?: boolean}): ReactNode {
return (
<g transform={`translate(${x} ${y})`}>
<rect x={-46} y={-22} width={92} height={44} rx={4} className={accent ? styles.boxAccent : styles.box} />
<text y={sub ? -3 : 5} className={styles.label} textAnchor="middle">{label}</text>
{sub && <text y={13} className={styles.sub} textAnchor="middle">{sub}</text>}
</g>
);
}

export function LandslideKitDiagram(): ReactNode {
return (
<Figure
title="A Grillo Slide kit on a slope"
caption="Rovers on the moving slope report through the radio mesh to the base on stable ground, which uploads over LTE.">
<path d="M20 300 L250 300 L560 120 L700 120" className={styles.ground} />
<path d="M250 300 L560 120 L560 300 L250 300 Z" className={styles.slope} />
<text x={470} y={272} className={styles.sub} textAnchor="middle">moving ground</text>
<text x={120} y={325} className={styles.sub} textAnchor="middle">stable ground</text>

<line x1={120} y1={246} x2={120} y2={110} className={styles.accentLine} markerEnd="url(#arrowAccent)" strokeDasharray="5 4" />
<text x={130} y={175} className={styles.accentText}>LTE</text>
<Device x={120} y={84} label="Grillo Cloud" sub="slide.grillo.io" />

<Device x={120} y={268} label="Base" sub="reference" accent />
<Device x={330} y={226} label="Rover" />
<Device x={430} y={168} label="Rover" />
<Device x={530} y={110} label="Rover" sub="2 hops" />

<line x1={166} y1={262} x2={284} y2={232} className={styles.mesh} />
<line x1={376} y1={214} x2={384} y2={180} className={styles.mesh} />
<line x1={476} y1={156} x2={484} y2={122} className={styles.mesh} />
<text x={226} y={232} className={styles.sub} textAnchor="middle">radio mesh</text>
</Figure>
);
}

export function WarningTimeDiagram(): ReactNode {
return (
<Figure
title="Blind zone and warning time"
caption="Near the epicentre, shaking arrives before an alert can go out. Farther away, the gap between the alert and the strong shaking grows.">
<line x1={60} y1={300} x2={680} y2={300} className={styles.axis} markerEnd="url(#arrow)" />
<text x={680} y={326} className={styles.sub} textAnchor="end">distance from epicentre</text>
<line x1={60} y1={300} x2={60} y2={40} className={styles.axis} markerEnd="url(#arrow)" />
<text x={50} y={34} className={styles.sub} textAnchor="start">time after the earthquake starts</text>

<line x1={60} y1={300} x2={660} y2={60} className={styles.wave} />
<text x={600} y={74} className={styles.label} textAnchor="end">strong shaking arrives</text>

<line x1={60} y1={196} x2={660} y2={196} className={styles.accentLine} strokeDasharray="6 4" />
<text x={656} y={188} className={styles.accentText} textAnchor="end">alert sent</text>

<rect x={60} y={196} width={260} height={104} className={styles.blind} />
<text x={312} y={268} className={styles.label} textAnchor="end">blind zone</text>
<text x={312} y={286} className={styles.sub} textAnchor="end">shaking before the alert</text>

<line x1={520} y1={196} x2={520} y2={116} className={styles.gap} markerStart="url(#arrow)" markerEnd="url(#arrow)" />
<text x={530} y={160} className={styles.label}>warning time</text>
</Figure>
);
}

function Box({x, y, w, label, sub, accent}: {x: number; y: number; w: number; label: string; sub: string; accent?: boolean}): ReactNode {
return (
<g transform={`translate(${x} ${y})`}>
<rect x={-w / 2} y={-26} width={w} height={52} rx={4} className={accent ? styles.boxAccent : styles.box} />
<text y={-4} className={styles.label} textAnchor="middle">{label}</text>
<text y={14} className={styles.sub} textAnchor="middle">{sub}</text>
</g>
);
}

export function DataPathDiagram(): ReactNode {
return (
<Figure
title="Where Grillo Pulse and Grillo One data goes"
caption="Health reports always go to Grillo Cloud. Seismic data goes to Grillo by default, or to your own server running coap2seis.">
<Box x={110} y={180} w={130} label="Pulse / One" sub="sensor" accent />

<line x1={175} y1={166} x2={448} y2={72} className={styles.line} markerEnd="url(#arrow)" />
<text x={300} y={98} className={styles.sub} textAnchor="middle">health 路 UDP 5683</text>
<Box x={560} y={64} w={220} label="Grillo Cloud" sub="status 路 health 路 firmware" />

<line x1={175} y1={180} x2={448} y2={180} className={styles.line} markerEnd="url(#arrow)" />
<text x={310} y={170} className={styles.sub} textAnchor="middle">seismic 路 UDP 5684 (default)</text>
<Box x={560} y={180} w={220} label="Grillo 路 SISTEM add-on" sub="events 路 waveforms" />

<line x1={175} y1={194} x2={448} y2={290} className={styles.line} strokeDasharray="5 4" markerEnd="url(#arrow)" />
<text x={240} y={262} className={styles.sub} textAnchor="middle">or, if Grillo sets it</text>
<Box x={560} y={296} w={220} label="Your server 路 coap2seis" sub="Earthworm 路 miniSEED" />
</Figure>
);
}
98 changes: 98 additions & 0 deletions src/components/Diagrams/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
.figure {
margin: 8px 0 28px;
padding: 16px;
border: 1px solid var(--border-hairline);
background: var(--bg-surface);
}

.svg {
display: block;
width: 100%;
height: auto;
}

.caption {
margin-top: 10px;
color: var(--fg-3);
font: 400 13px/1.5 var(--font-sans);
}

.label {
fill: var(--fg-1);
font: 500 13px var(--font-sans);
}

.sub {
fill: var(--fg-3);
font: 400 11px var(--font-mono);
}

.box {
fill: var(--bg-elevated);
stroke: var(--border-strong);
stroke-width: 1;
}

.boxAccent {
fill: var(--bg-elevated);
stroke: var(--brand-orange);
stroke-width: 1.5;
}

.line,
.axis {
stroke: var(--fg-3);
stroke-width: 1.25;
fill: none;
}

.mesh {
stroke: var(--fg-3);
stroke-width: 1.25;
stroke-dasharray: 3 3;
}

.gap {
stroke: var(--fg-2);
stroke-width: 1.25;
}

.arrowHead {
fill: var(--fg-3);
}

.accentLine {
stroke: var(--brand-orange);
stroke-width: 1.5;
fill: none;
}

.accentFill {
fill: var(--brand-orange);
}

.accentText {
fill: var(--brand-orange);
font: 500 12px var(--font-mono);
}

.wave {
stroke: var(--fg-1);
stroke-width: 2;
}

.ground {
stroke: var(--fg-3);
stroke-width: 1.5;
fill: none;
}

.slope {
fill: var(--brand-orange);
opacity: 0.08;
}

.blind {
fill: var(--brand-orange);
opacity: 0.12;
}
Loading