+
+
+
Accelerometer Data (m/s2)
+ Info +
Live visualization of raw accelerometer data (X, Y, Z axes) from the board's Movement module. Horizontal axis represents time; vertical axis shows acceleration in m/s2
+
+
+ +
+
+
+ X +
+
+
+ Y +
+
+
+ Z +
+
+
+
+ No data +

No data

+
+
+ +
+
+
+
+
+
+ + Info +
It quantifies how far the current vibration is from the model's 'normal' clusters.
+ + Scores above your threshold are flagged as an anomaly.
+
+ +
+
+ 1 +
+
+
5
+ +
+ 10 +
+
+
+
+ +
+ +
-
- -
- -
- No anomaly +
+
+
+

Recent Anomalies

+
+
    +
    +
    diff --git a/examples/vibration-anomaly-detection/assets/style.css b/examples/vibration-anomaly-detection/assets/style.css index 9d37cdc..a4c41c9 100644 --- a/examples/vibration-anomaly-detection/assets/style.css +++ b/examples/vibration-anomaly-detection/assets/style.css @@ -4,20 +4,22 @@ * SPDX-License-Identifier: MPL-2.0 */ -@import url("fonts/roboto-mono.css"); +@import url("fonts/fonts.css"); /* * This CSS is used to center the various elements on the screen */ * { + box-sizing: border-box; margin: 0; padding: 0; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; - background-color: #ECF1F1; - color: #2C353A; + background-color: #DAE3E3; + line-height: 1.6; + color: #343a40; padding: 24px 40px; } @@ -25,18 +27,19 @@ body { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 32px; + margin-bottom: 24px; + padding: 12px 0; } .arduino-text { color: #008184; font-family: "Roboto Mono", monospace; font-size: 20px; - font-weight: 600; + font-weight: 700; margin: 0; font-style: normal; line-height: 170%; - letter-spacing: 0.28px; + letter-spacing: 2.4px; } .arduino-logo { @@ -44,84 +47,430 @@ body { width: auto; } -.container { - text-align: center; +.main-content { + display: flex; + gap: 30px; + align-items: stretch; } /* - * LED Button styling + * Styles for specific components required by Anomaly Detection */ -.led-container { + +.legend { display: flex; - justify-content: center; - margin-bottom: 32px; - padding-top: 40px; + gap: 16px; + margin-top: 8px; + font-size: 14px; } -#fan-led { - width: 128px; - height: 128px; - border-radius: 50%; - cursor: pointer; - transition: all 0.3s ease; +.legend-item { display: flex; align-items: center; - justify-content: center; - font-family: inherit; - font-weight: 600; - font-size: 14px; - text-align: center; - line-height: 1.2; - outline: none; - position: relative; - border: 2px solid #C9D2D2; + gap: 6px; } -#fan-led.led-off { - background: #008184; - color: #ffffff; - box-shadow: 0 0 20px #008184, 0 0 40px #008184, 0 0 60px #008184; - border-color: #008184; +.legend-color { + width: 12px; + height: 4px; + border-radius: 1px; } -#fan-led.led-on { - background: #e00d0d; - color: #ffffff; - box-shadow: 0 0 20px #e00d0d, 0 0 40px #e00d0d, 0 0 60px #e00d0d; - border-color: #e00d0d; +.controls-section-right { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; } -#fan-led:hover { - transform: scale(1.05); +.box-title { + color: #2C353A; + font-family: "Roboto Mono"; + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 170%; + letter-spacing: 1.2px; + margin-bottom: 16px; } -#fan-led:active { - transform: scale(0.95); +.controls-section-left { + background: #ECF1F1; + padding: 16px; + border-radius: 8px; + min-width: 750px; + width: 100%; + display: flex; + flex-direction: column; } -.instruction-text { - font-size: 14px; - font-style: normal; - font-weight: 400; - line-height: 160%; - letter-spacing: 0.12px; +.right-column { + display: flex; + flex-direction: column; + gap: 32px; + width: 100%; + max-width: 550px; +} + +.right-column .container:last-child { + flex-grow: 1; +} + +.container-right { + background: #ECF1F1; + padding: 16px; + border-radius: 8px; +} + +.error-message { + margin-top: 20px; + padding: 10px; + border-radius: 5px; + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.recent-scans-title-container { + display: flex; + align-items: center; + gap: 8px; + justify-content: space-between; +} + +.recent-scans-title { color: #2C353A; + font-family: "Roboto Mono", monospace; + font-size: 12px; + font-style: normal; + font-weight: 700; + line-height: 170%; + letter-spacing: 1.2px; + margin: 0; +} + +#recentClassifications { + list-style-type: none; + padding: 0; + flex: 1; } /* * Responsive design */ @media (max-width: 768px) { - body { - padding: 12px 20px; + .main-content { + flex-direction: column; + } + + .right-column { + max-width: 100%; } .arduino-text { font-size: 14px; } + .container { + padding: 15px; + } + + .controls-section-left { + min-width: 330px; + } + .arduino-logo { - height: 20px; + height: 16px; width: auto; } -} \ No newline at end of file + +} + +@media (max-width: 1024px) and (min-width: 769px) { + .controls-section-left { + min-width: 490px; + } +} + +@media (max-width: 480px) { + .controls-section-left { + min-width: 170px; + } +} + +.info-btn { + width: 14px; + height: 14px; + cursor: pointer; + border-radius: 50%; + background-color: #C9D2D2; + padding: 2px; + transition: background 0.2s; + position: relative; +} + +.popover { + position: absolute; + left: 5%; + top: 70%; + margin-left: 8px; + display: none; + background: #fff; + padding: 16px 24px; + border-radius: 6px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + z-index: 10; + width: 300px; + color: #2C353A; + font-weight: 100; + font-family: "Open Sans"; + font-size: 12px; + line-height: 170%; + letter-spacing: 0.12px; +} + +.popover.active { + display: block; +} + +.no-recent-anomalies { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: #5D6A6B; + gap: 8px; + margin: auto; +} + +.no-recent-anomalies p { + font-family: "Open Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 160%; /* 19.2px */ + letter-spacing: 0.12px; +} + +.feedback-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; +} + +.feedback-text { + font-family: "Open Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 160%; /* 19.2px */ + letter-spacing: 0.12px; +} + +.no-data-placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + color: #5D6A6B; + gap: 8px; + margin: auto; +} + +.no-data-placeholder p { + font-family: "Open Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 160%; /* 19.2px */ + letter-spacing: 0.12px; +} + +.control-group { + position: relative; +} + +.slider-box { + display: flex; + align-items: center; + gap: 10px; +} + +.control-confidence { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} + +#confidenceSlider { + width: 100%; + height: 6px; + border-radius: 3px; + background: #DAE3E3; + outline: none; + -webkit-appearance: none; + appearance: none; + position: relative; + margin: 20px 0 10px 0; +} + +#confidenceSlider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 12px; + height: 12px; + border-radius: 50%; + background: #008184; + cursor: pointer; + position: relative; + bottom: 3px; + z-index: 2; +} + +#confidenceSlider::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + border-radius: 3px; + background: #DAE3E3; +} + +#confidenceSlider::-moz-range-track { + width: 100%; + height: 6px; + border-radius: 3px; + background: #DAE3E3; + border: none; +} + +.slider-container { + position: relative; + width: 100%; +} + +.slider-progress { + position: absolute; + top: 20px; + left: 0; + height: 6px; + background: #008184; + border-radius: 3px; + pointer-events: none; + z-index: 1; + transition: width 0.1s ease; +} + +.confidence-value-display { + position: absolute; + top: -3px; + transform: translateX(-50%); + color: #008184; + padding: 2px 6px; + pointer-events: none; + z-index: 3; + white-space: nowrap; + transition: left 0.1s ease; + font-family: "Open Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 160%; + letter-spacing: 0.12px; +} + +.confidence-limits { + color: #2C353A; + font-family: "Open Sans"; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 160%; + letter-spacing: 0.12px; + margin-top: 10px; +} + +.btn-tertiary { + border-radius: 6px; + border: 1px solid #C9D2D2; + background: white; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + padding: 4px 8px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 12px; + min-width: 50px; + height: 36px; +} + +.confidence-input { + border: none; + background: transparent; + font-size: 12px; + font-weight: inherit; + color: inherit; + text-align: center; + width: 40px; + padding: 0; + margin: 0; + outline: none; + cursor: text; +} + +.confidence-input:focus { + background: rgba(0, 129, 132, 0.1); + border-radius: 2px; +} + +.reset-icon { + width: 18px; + height: 18px; + opacity: 0.7; + transition: opacity 0.3s ease; + cursor: pointer; +} + +.reset-icon svg path { + fill: black; +} + +.btn-tertiary:hover .reset-icon { + opacity: 1; +} + +.anomaly-list-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 0; + border-bottom: 1px solid #DAE3E3; +} +.anomaly-score { + font-weight: normal; + color: #2C353A; + font-size: 12px; +} +.anomaly-text { + color: #2C353A; + font-size: 12px; +} +.anomaly-time { + color: #2C353A; + font-size: 12px; +} +#accelerometer-data-display { + margin-top: 20px; +} + +#plot { + width: 100%; + height: 250px; +} diff --git a/examples/vibration-anomaly-detection/python/main.py b/examples/vibration-anomaly-detection/python/main.py index f79b877..bf6a6c1 100644 --- a/examples/vibration-anomaly-detection/python/main.py +++ b/examples/vibration-anomaly-detection/python/main.py @@ -2,6 +2,11 @@ # # SPDX-License-Identifier: MPL-2.0 +import json +import time +import math +import threading +from datetime import datetime from arduino.app_utils import * from arduino.app_bricks.web_ui import WebUI from arduino.app_bricks.vibration_anomaly_detection import VibrationAnomalyDetection @@ -10,8 +15,16 @@ ui = WebUI() +# --- Configuration Constant --- +USE_FAKE_DATA = False # Set to True to use simulated data, False to use real sensor data via Bridge RPC +# ------------------------------ + vibration_detection = VibrationAnomalyDetection(anomaly_detection_threshold=1.0) +def on_override_th(value: float): + logger.info(f"Setting new anomaly threshold: {value}") + vibration_detection.anomaly_detection_threshold = value + def get_fan_status(anomaly_detected: bool): return { "anomaly": anomaly_detected, @@ -22,6 +35,11 @@ def get_fan_status(anomaly_detected: bool): # Register action to take after successful detection def on_detected_anomaly(anomaly_score: float, classification: dict): print(f"Detected anomaly. Score: {anomaly_score}") + anomaly_payload = { + "score": anomaly_score, + "timestamp": datetime.now().isoformat() + } + ui.send_message('anomaly_detected', json.dumps(anomaly_payload)) ui.send_message('fan_status_update', get_fan_status(True)) vibration_detection.on_anomaly(on_detected_anomaly) @@ -34,6 +52,9 @@ def record_sensor_movement(x: float, y: float, z: float): y_ms2 = y * 9.81 z_ms2 = z * 9.81 + # Forward raw data to UI for plotting + ui.send_message('sample', {'x': x_ms2, 'y': y_ms2, 'z': z_ms2}) + # Forward samples to the vibration_detection brick vibration_detection.accumulate_samples((x_ms2, y_ms2, z_ms2)) @@ -41,13 +62,37 @@ def record_sensor_movement(x: float, y: float, z: float): logger.exception(f"record_sensor_movement: Error: {e}") print(f"record_sensor_movement: Error: {e}") -# Register the Bridge RPC provider so the sketch can call into Python -try: - logger.debug("Registering 'record_sensor_movement' Bridge provider") - Bridge.provide("record_sensor_movement", record_sensor_movement) - logger.debug("'record_sensor_movement' registered successfully") -except RuntimeError: - logger.debug("'record_sensor_movement' already registered") +if USE_FAKE_DATA: + # Faking sensor data for UI testing + def generate_fake_data(): + logger.info("Starting fake data generation") + start_time = time.time() + while True: + current_time = time.time() - start_time + # Generate wave patterns for x, y, and z + x = 0.5 * math.sin(2 * math.pi * 0.5 * current_time) # 0.5 Hz frequency + y = 0.3 * math.sin(2 * math.pi * 1.0 * current_time + (math.pi / 2)) # 1.0 Hz frequency, 90-degree phase shift + z = 0.8 * math.cos(2 * math.pi * 0.2 * current_time) # 0.2 Hz frequency, using cosine + + # Introduce a sudden anomaly occasionally + if int(current_time) % 20 == 0 and int(current_time) != 0: + x += 0.5 * math.sin(2 * math.pi * 10 * current_time) # high frequency noise + y += 0.5 * math.cos(2 * math.pi * 10 * current_time) + + record_sensor_movement(x, y, z) + time.sleep(0.1) # 10 Hz data rate + + fake_data_thread = threading.Thread(target=generate_fake_data, daemon=True) + fake_data_thread.start() +else: + # Register the Bridge RPC provider so the sketch can call into Python + try: + logger.debug("Registering 'record_sensor_movement' Bridge provider") + Bridge.provide("record_sensor_movement", record_sensor_movement) + logger.debug("'record_sensor_movement' registered successfully") + except RuntimeError: + logger.debug("'record_sensor_movement' already registered") + # Let the App runtime manage bricks and run the web server App.run() diff --git a/examples/vibration-anomaly-detection/sketch/sketch.ino b/examples/vibration-anomaly-detection/sketch/sketch.ino index 47d24b5..a65cc2c 100644 --- a/examples/vibration-anomaly-detection/sketch/sketch.ino +++ b/examples/vibration-anomaly-detection/sketch/sketch.ino @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MPL-2.0 #include -#include +#include // Create a ModulinoMovement object ModulinoMovement movement; diff --git a/examples/vibration-anomaly-detection/sketch/sketch.yaml b/examples/vibration-anomaly-detection/sketch/sketch.yaml index fc88d11..a3d6ef3 100644 --- a/examples/vibration-anomaly-detection/sketch/sketch.yaml +++ b/examples/vibration-anomaly-detection/sketch/sketch.yaml @@ -8,7 +8,7 @@ profiles: - DebugLog (0.8.4) - ArxContainer (0.7.0) - ArxTypeTraits (0.3.1) - - Modulino (0.5.0) + - Arduino_Modulino (0.6.1) - Arduino_HS300x (1.0.0) - Arduino_LPS22HB (1.0.2) - Arduino_LSM6DSOX (1.1.2)