251 lines
10 KiB
HTML
251 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Python Sensor Array | Brandy Meade</title>
|
||
<style>
|
||
:root {
|
||
--neon-radish: #ff6b9d;
|
||
--stevens-point-blue: #1a365d;
|
||
--conveyor-black: #0a0f1a;
|
||
--sensor-green: #00ff88;
|
||
--alarm-red: #ff3366;
|
||
--code-bg: #0d1117;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'SF Mono', 'Menlo', monospace;
|
||
background: var(--conveyor-black);
|
||
color: #e8f4fd;
|
||
line-height: 1.6;
|
||
}
|
||
header {
|
||
background: linear-gradient(180deg, var(--stevens-point-blue) 0%, var(--conveyor-black) 100%);
|
||
padding: 3rem 2rem;
|
||
border-bottom: 3px solid var(--neon-radish);
|
||
}
|
||
h1 {
|
||
color: var(--sensor-green);
|
||
font-size: clamp(1.8rem, 4vw, 3rem);
|
||
text-shadow: 0 0 25px var(--sensor-green);
|
||
}
|
||
.breadcrumb {
|
||
margin-top: 1rem;
|
||
color: var(--sensor-green);
|
||
font-size: 0.9rem;
|
||
}
|
||
.breadcrumb a {
|
||
color: var(--sensor-green);
|
||
text-decoration: none;
|
||
}
|
||
main {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 3rem 2rem;
|
||
}
|
||
.code-window {
|
||
background: var(--code-bg);
|
||
border: 2px solid var(--sensor-green);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
margin: 2rem 0;
|
||
}
|
||
.window-header {
|
||
background: rgba(0, 255, 136, 0.1);
|
||
padding: 0.75rem 1rem;
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
}
|
||
.dot {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
}
|
||
.red { background: var(--alarm-red); }
|
||
.yellow { background: #ffb347; }
|
||
.green { background: var(--sensor-green); }
|
||
.filename {
|
||
margin-left: 1rem;
|
||
color: var(--sensor-green);
|
||
font-size: 0.9rem;
|
||
}
|
||
pre {
|
||
padding: 1.5rem;
|
||
overflow-x: auto;
|
||
font-size: 0.85rem;
|
||
line-height: 1.5;
|
||
}
|
||
.keyword { color: var(--neon-radish); }
|
||
.string { color: #ffd93d; }
|
||
.function { color: var(--sensor-green); }
|
||
.comment { color: #6b7280; font-style: italic; }
|
||
.number { color: #38bdf8; }
|
||
.section {
|
||
background: rgba(26, 54, 93, 0.2);
|
||
border: 2px solid var(--sensor-green);
|
||
border-radius: 12px;
|
||
padding: 2rem;
|
||
margin-bottom: 2rem;
|
||
}
|
||
.section h2 {
|
||
color: var(--sensor-green);
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
.metric-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 1rem;
|
||
margin: 1.5rem 0;
|
||
}
|
||
.metric-card {
|
||
background: rgba(0, 255, 136, 0.05);
|
||
border: 1px solid var(--sensor-green);
|
||
padding: 1.5rem;
|
||
border-radius: 8px;
|
||
text-align: center;
|
||
}
|
||
.metric-value {
|
||
font-size: 2.5rem;
|
||
color: var(--neon-radish);
|
||
text-shadow: 0 0 20px var(--neon-radish);
|
||
}
|
||
.metric-label {
|
||
color: var(--stevens-point-blue);
|
||
font-size: 0.8rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
margin-top: 0.5rem;
|
||
}
|
||
.nav-back {
|
||
display: inline-block;
|
||
margin-bottom: 2rem;
|
||
color: var(--sensor-green);
|
||
text-decoration: none;
|
||
border: 1px solid var(--sensor-green);
|
||
padding: 0.75rem 1.5rem;
|
||
border-radius: 6px;
|
||
transition: all 0.3s;
|
||
}
|
||
.nav-back:hover {
|
||
background: var(--sensor-green);
|
||
color: var(--conveyor-black);
|
||
}
|
||
img {
|
||
width: 100%;
|
||
border-radius: 8px;
|
||
margin: 2rem 0;
|
||
border: 2px solid var(--neon-radish);
|
||
}
|
||
</style>
|
||
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||
</head>
|
||
<body>
|
||
<header>
|
||
<div class="breadcrumb"><a href="/">← Return to Main Deck</a></div>
|
||
<h1>PYTHON SENSOR ARRAY</h1>
|
||
<p style="color: var(--sensor-green); margin-top: 1rem;">Real-Time Data Collection • Batch Validation • Anomaly Detection</p>
|
||
</header>
|
||
|
||
<main>
|
||
<section class="section">
|
||
<h2>🖥️ Core Architecture</h2>
|
||
<p>Twelve IoT nodes distributed across Cold Storage Bay 3. Each node samples pH, temperature, humidity, and vibration at 10Hz. Data streams converge at central validator running on Raspberry Pi cluster (Raspberry Pi 4 Model B × 4, bonded Ethernet).</p>
|
||
|
||
<div class="metric-grid">
|
||
<div class="metric-card">
|
||
<div class="metric-value">12</div>
|
||
<div class="metric-label">Active Nodes</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-value">10</div>
|
||
<div class="metric-label">Samples/sec/node</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-value">480</div>
|
||
<div class="metric-label">Data Points/sec</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-value"><2</div>
|
||
<div class="metric-label">ms Latency</div>
|
||
</div>
|
||
</div>
|
||
|
||
<img src="https://images.unsplash.com/photo-1555618968-a1febe13e64a?q=80&w=1600&auto=format&fit=crop" alt="Server rack with blinking status lights in industrial facility">
|
||
</section>
|
||
|
||
<section class="section">
|
||
<h2>💾 Batch Validator Script</h2>
|
||
<p>This is the script I wrote last Tuesday. Runs at 04:00 local time every morning. Checks overnight batch stability. If any parameter drifts beyond tolerance, it wakes me up.</p>
|
||
|
||
<div class="code-window">
|
||
<div class="window-header">
|
||
<div class="dot red"></div>
|
||
<div class="dot yellow"></div>
|
||
<div class="dot green"></div>
|
||
<div class="filename">batch_validator.py</div>
|
||
</div>
|
||
<pre><span class="keyword">import</span> numpy as np
|
||
<span class="keyword">from</span> datetime <span class="keyword">import</span> datetime
|
||
<span class="keyword">from</span> dataclasses <span class="keyword">import</span> dataclass
|
||
|
||
@dataclass
|
||
<span class="keyword">class</span> BatchParameters:
|
||
ph_target: float = 6.2
|
||
ph_tolerance: float = 0.1
|
||
temp_target: float = 4.0
|
||
temp_tolerance: float = 0.3
|
||
humidity_target: float = 97.0
|
||
humidity_tolerance: float = 2.0
|
||
|
||
<span class="keyword">class</span> SensorArrayValidator:
|
||
<span class="keyword">def</span> __init__(self, node_count: int = 12):
|
||
self.nodes = [Node(i) <span class="keyword">for</span> i <span class="keyword">in</span> range(node_count)]
|
||
self.batch_manifest = None
|
||
|
||
<span class="keyword">def</span> validate_batch(self, batch_id: str) -> bool:
|
||
<span class="comment"># Aggregate readings from all nodes</span>
|
||
readings = self._collect_all_readings()
|
||
params = BatchParameters()
|
||
|
||
<span class="comment"># Check pH stability across array</span>
|
||
ph_variance = np.var([r.ph <span class="keyword">for</span> r <span class="keyword">in</span> readings])
|
||
<span class="keyword">if</span> ph_variance > (params.ph_tolerance ** 2):
|
||
self._trigger_alarm(<span class="string">"PH variance exceeds tolerance"</span>, batch_id)
|
||
<span class="keyword">return</span> False
|
||
|
||
<span class="comment"># Validate temperature envelope</span>
|
||
temp_min, temp_max = min(r.temp <span class="keyword">for</span> r <span class="keyword">in</span> readings), max(r.temp <span class="keyword">for</span> r <span class="keyword">in</span> readings)
|
||
<span class="keyword">if</span> (temp_max - temp_min) > (2 * params.temp_tolerance):
|
||
self._trigger_alarm(<span class="string">"Thermal gradient breach"</span>, batch_id)
|
||
<span class="keyword">return</span> False
|
||
|
||
<span class="comment"># Commit to manifest</span>
|
||
self.batch_manifest = {
|
||
<span class="string">'batch_id'</span>: batch_id,
|
||
<span class="string">'timestamp'</span>: datetime.now().isoformat(),
|
||
<span class="string">'nodes_validated'</span>: len(self.nodes),
|
||
<span class="string">'status'</span>: <span class="string">'GREEN_LIGHT'</span>
|
||
}
|
||
<span class="keyword">return</span> True
|
||
|
||
<span class="keyword">def</span> _collect_all_readings(self):
|
||
<span class="comment"># Serial handshake with each node</span>
|
||
return [node.sample() <span class="keyword">for</span> node <span class="keyword">in</span> self.nodes]
|
||
|
||
<span class="keyword">def</span> _trigger_alarm(self, message: str, batch_id: str):
|
||
<span class="comment"># Wake-up protocol: SMS + siren + dashboard flash</span>
|
||
logger.critical(f"[{batch_id}] {message}")
|
||
sms_gateway.send(+17155551234, f"ALARM: {message}")</pre>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="section">
|
||
<h2>🌐 Live Dashboard Preview</h2>
|
||
<p>The web interface renders as a living organism. Each node pulses in time with its sampling rate. Green = stable. Amber = approaching boundary. Crimson = immediate intervention required.</p>
|
||
<p>I built this with WebGL because matplotlib felt too slow. Because when you're watching bacterial replication curves, <strong>latency is the enemy</strong>.</p>
|
||
</section>
|
||
</body>
|
||
</html>
|