Spaces:
Sleeping
Sleeping
GitHub Actions
commited on
Commit
·
a1edd1c
1
Parent(s):
1878b82
Deploy from GitHub Actions
Browse files- .devcontainer/devcontainer.json +17 -0
- .gitattributes +0 -35
- LICENSE +121 -0
- README.md +5 -10
- app.py +89 -0
- data/raw_data.csv +0 -0
- data_logger.py +37 -0
- generate_data.py +52 -0
- hardware/esp32_sensor_code.ino +40 -0
- models/regression_anomaly_threshold.npy +0 -0
- models/vibration_regressor.json +0 -0
- requirements.txt +5 -0
- train_model.py +68 -0
.devcontainer/devcontainer.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "Predictive Maintenance MVP",
|
| 3 |
+
"image": "mcr.microsoft.com/devcontainers/python:3.10-bullseye",
|
| 4 |
+
"customizations": {
|
| 5 |
+
"vscode": {
|
| 6 |
+
"extensions": [
|
| 7 |
+
"ms-python.python",
|
| 8 |
+
"ms-python.vscode-pylance",
|
| 9 |
+
"ms-toolsai.jupyter",
|
| 10 |
+
"vsciot-vscode.vscode-arduino"
|
| 11 |
+
]
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
"forwardPorts": [5000, 7860],
|
| 15 |
+
"postCreateCommand": "pip install --user -r requirements.txt",
|
| 16 |
+
"remoteUser": "vscode"
|
| 17 |
+
}
|
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LICENSE
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Creative Commons Legal Code
|
| 2 |
+
|
| 3 |
+
CC0 1.0 Universal
|
| 4 |
+
|
| 5 |
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
| 6 |
+
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
| 7 |
+
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
| 8 |
+
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
| 9 |
+
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
| 10 |
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
| 11 |
+
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
| 12 |
+
HEREUNDER.
|
| 13 |
+
|
| 14 |
+
Statement of Purpose
|
| 15 |
+
|
| 16 |
+
The laws of most jurisdictions throughout the world automatically confer
|
| 17 |
+
exclusive Copyright and Related Rights (defined below) upon the creator
|
| 18 |
+
and subsequent owner(s) (each and all, an "owner") of an original work of
|
| 19 |
+
authorship and/or a database (each, a "Work").
|
| 20 |
+
|
| 21 |
+
Certain owners wish to permanently relinquish those rights to a Work for
|
| 22 |
+
the purpose of contributing to a commons of creative, cultural and
|
| 23 |
+
scientific works ("Commons") that the public can reliably and without fear
|
| 24 |
+
of later claims of infringement build upon, modify, incorporate in other
|
| 25 |
+
works, reuse and redistribute as freely as possible in any form whatsoever
|
| 26 |
+
and for any purposes, including without limitation commercial purposes.
|
| 27 |
+
These owners may contribute to the Commons to promote the ideal of a free
|
| 28 |
+
culture and the further production of creative, cultural and scientific
|
| 29 |
+
works, or to gain reputation or greater distribution for their Work in
|
| 30 |
+
part through the use and efforts of others.
|
| 31 |
+
|
| 32 |
+
For these and/or other purposes and motivations, and without any
|
| 33 |
+
expectation of additional consideration or compensation, the person
|
| 34 |
+
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
| 35 |
+
is an owner of Copyright and Related Rights in the Work, voluntarily
|
| 36 |
+
elects to apply CC0 to the Work and publicly distribute the Work under its
|
| 37 |
+
terms, with knowledge of his or her Copyright and Related Rights in the
|
| 38 |
+
Work and the meaning and intended legal effect of CC0 on those rights.
|
| 39 |
+
|
| 40 |
+
1. Copyright and Related Rights. A Work made available under CC0 may be
|
| 41 |
+
protected by copyright and related or neighboring rights ("Copyright and
|
| 42 |
+
Related Rights"). Copyright and Related Rights include, but are not
|
| 43 |
+
limited to, the following:
|
| 44 |
+
|
| 45 |
+
i. the right to reproduce, adapt, distribute, perform, display,
|
| 46 |
+
communicate, and translate a Work;
|
| 47 |
+
ii. moral rights retained by the original author(s) and/or performer(s);
|
| 48 |
+
iii. publicity and privacy rights pertaining to a person's image or
|
| 49 |
+
likeness depicted in a Work;
|
| 50 |
+
iv. rights protecting against unfair competition in regards to a Work,
|
| 51 |
+
subject to the limitations in paragraph 4(a), below;
|
| 52 |
+
v. rights protecting the extraction, dissemination, use and reuse of data
|
| 53 |
+
in a Work;
|
| 54 |
+
vi. database rights (such as those arising under Directive 96/9/EC of the
|
| 55 |
+
European Parliament and of the Council of 11 March 1996 on the legal
|
| 56 |
+
protection of databases, and under any national implementation
|
| 57 |
+
thereof, including any amended or successor version of such
|
| 58 |
+
directive); and
|
| 59 |
+
vii. other similar, equivalent or corresponding rights throughout the
|
| 60 |
+
world based on applicable law or treaty, and any national
|
| 61 |
+
implementations thereof.
|
| 62 |
+
|
| 63 |
+
2. Waiver. To the greatest extent permitted by, but not in contravention
|
| 64 |
+
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
| 65 |
+
irrevocably and unconditionally waives, abandons, and surrenders all of
|
| 66 |
+
Affirmer's Copyright and Related Rights and associated claims and causes
|
| 67 |
+
of action, whether now known or unknown (including existing as well as
|
| 68 |
+
future claims and causes of action), in the Work (i) in all territories
|
| 69 |
+
worldwide, (ii) for the maximum duration provided by applicable law or
|
| 70 |
+
treaty (including future time extensions), (iii) in any current or future
|
| 71 |
+
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
| 72 |
+
including without limitation commercial, advertising or promotional
|
| 73 |
+
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
| 74 |
+
member of the public at large and to the detriment of Affirmer's heirs and
|
| 75 |
+
successors, fully intending that such Waiver shall not be subject to
|
| 76 |
+
revocation, rescission, cancellation, termination, or any other legal or
|
| 77 |
+
equitable action to disrupt the quiet enjoyment of the Work by the public
|
| 78 |
+
as contemplated by Affirmer's express Statement of Purpose.
|
| 79 |
+
|
| 80 |
+
3. Public License Fallback. Should any part of the Waiver for any reason
|
| 81 |
+
be judged legally invalid or ineffective under applicable law, then the
|
| 82 |
+
Waiver shall be preserved to the maximum extent permitted taking into
|
| 83 |
+
account Affirmer's express Statement of Purpose. In addition, to the
|
| 84 |
+
extent the Waiver is so judged Affirmer hereby grants to each affected
|
| 85 |
+
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
| 86 |
+
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
| 87 |
+
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
| 88 |
+
maximum duration provided by applicable law or treaty (including future
|
| 89 |
+
time extensions), (iii) in any current or future medium and for any number
|
| 90 |
+
of copies, and (iv) for any purpose whatsoever, including without
|
| 91 |
+
limitation commercial, advertising or promotional purposes (the
|
| 92 |
+
"License"). The License shall be deemed effective as of the date CC0 was
|
| 93 |
+
applied by Affirmer to the Work. Should any part of the License for any
|
| 94 |
+
reason be judged legally invalid or ineffective under applicable law, such
|
| 95 |
+
partial invalidity or ineffectiveness shall not invalidate the remainder
|
| 96 |
+
of the License, and in such case Affirmer hereby affirms that he or she
|
| 97 |
+
will not (i) exercise any of his or her remaining Copyright and Related
|
| 98 |
+
Rights in the Work or (ii) assert any associated claims and causes of
|
| 99 |
+
action with respect to the Work, in either case contrary to Affirmer's
|
| 100 |
+
express Statement of Purpose.
|
| 101 |
+
|
| 102 |
+
4. Limitations and Disclaimers.
|
| 103 |
+
|
| 104 |
+
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
| 105 |
+
surrendered, licensed or otherwise affected by this document.
|
| 106 |
+
b. Affirmer offers the Work as-is and makes no representations or
|
| 107 |
+
warranties of any kind concerning the Work, express, implied,
|
| 108 |
+
statutory or otherwise, including without limitation warranties of
|
| 109 |
+
title, merchantability, fitness for a particular purpose, non
|
| 110 |
+
infringement, or the absence of latent or other defects, accuracy, or
|
| 111 |
+
the present or absence of errors, whether or not discoverable, all to
|
| 112 |
+
the greatest extent permissible under applicable law.
|
| 113 |
+
c. Affirmer disclaims responsibility for clearing rights of other persons
|
| 114 |
+
that may apply to the Work or any use thereof, including without
|
| 115 |
+
limitation any person's Copyright and Related Rights in the Work.
|
| 116 |
+
Further, Affirmer disclaims responsibility for obtaining any necessary
|
| 117 |
+
consents, permissions or other rights required for any use of the
|
| 118 |
+
Work.
|
| 119 |
+
d. Affirmer understands and acknowledges that Creative Commons is not a
|
| 120 |
+
party to this document and has no duty or obligation with respect to
|
| 121 |
+
this CC0 or use of the Work.
|
README.md
CHANGED
|
@@ -1,14 +1,9 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
emoji: 😻
|
| 4 |
colorFrom: red
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
-
|
| 11 |
-
short_description: deploys the mvp for engine vibration for zodiac boat engine
|
| 12 |
-
---
|
| 13 |
-
|
| 14 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
title: Predictive Maintenance Detector
|
| 2 |
+
emoji: 🚨
|
|
|
|
| 3 |
colorFrom: red
|
| 4 |
+
colorTo: yellow
|
| 5 |
sdk: gradio
|
| 6 |
+
sdk_version: 3.41.2
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
+
---
|
|
|
|
|
|
|
|
|
|
|
|
app.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- app.py (Regression Version) ---
|
| 2 |
+
import gradio as gr
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import os
|
| 6 |
+
import xgboost as xgb
|
| 7 |
+
|
| 8 |
+
# --- Load Model and Threshold ---
|
| 9 |
+
base_dir = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
+
model_path = os.path.join(base_dir, 'models', 'vibration_regressor.json')
|
| 11 |
+
threshold_path = os.path.join(base_dir, 'models', 'regression_anomaly_threshold.npy')
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
model = xgb.XGBRegressor()
|
| 15 |
+
model.load_model(model_path)
|
| 16 |
+
anomaly_threshold = np.load(threshold_path)
|
| 17 |
+
print("Model and threshold loaded successfully.")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
print(f"CRITICAL ERROR: Could not load files. Details: {e}")
|
| 20 |
+
model = None
|
| 21 |
+
anomaly_threshold = None
|
| 22 |
+
|
| 23 |
+
description = """
|
| 24 |
+
<div align="center">
|
| 25 |
+
<img src="https://images.pexels.com/photos/6510296/pexels-photo-6510296.jpeg" width="600px" />
|
| 26 |
+
</div>
|
| 27 |
+
**Zodiac 1.0: Regression-Based Engine Diagnostics.**
|
| 28 |
+
This advanced model predicts the **normal** vibration for your engine based on its current operating conditions. Enter the conditions and the actual vibration reading from your sensor to check for anomalies.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def predict_anomaly(rpm, temp, fuel, sea_state, actual_vibration):
|
| 32 |
+
"""Predicts if the actual vibration is an anomaly given the conditions."""
|
| 33 |
+
if model is None:
|
| 34 |
+
return "Error: Model not loaded.", "", ""
|
| 35 |
+
|
| 36 |
+
try:
|
| 37 |
+
# Map sea state string to integer
|
| 38 |
+
sea_state_map = {"Calm": 0, "Choppy": 1, "Stormy": 2}
|
| 39 |
+
sea_state_int = sea_state_map.get(sea_state, 0)
|
| 40 |
+
|
| 41 |
+
# Create a DataFrame with the exact feature names the model was trained on
|
| 42 |
+
input_data = pd.DataFrame([{
|
| 43 |
+
'rpm': rpm,
|
| 44 |
+
'ambient_temp_c': temp,
|
| 45 |
+
'fuel_level_percent': fuel,
|
| 46 |
+
'sea_state': sea_state_int
|
| 47 |
+
}])
|
| 48 |
+
|
| 49 |
+
# Predict the expected normal vibration
|
| 50 |
+
predicted_vibration = model.predict(input_data)[0]
|
| 51 |
+
|
| 52 |
+
# Calculate the error
|
| 53 |
+
error = abs(actual_vibration - predicted_vibration)
|
| 54 |
+
|
| 55 |
+
status = "🚨 ANOMALY DETECTED 🚨" if error > anomaly_threshold else "✅ Machine State: Normal"
|
| 56 |
+
|
| 57 |
+
details = f"Predicted Normal Vibration: {predicted_vibration:.2f}\n"
|
| 58 |
+
details += f"Actual Measured Vibration: {actual_vibration:.2f}\n"
|
| 59 |
+
details += f"Calculated Error: {error:.4f}\n"
|
| 60 |
+
details += f"Anomaly Threshold: {anomaly_threshold:.4f}"
|
| 61 |
+
|
| 62 |
+
return status, details
|
| 63 |
+
except Exception as e:
|
| 64 |
+
return f"An error occurred: {e}", ""
|
| 65 |
+
|
| 66 |
+
# --- Create the Gradio Interface with sliders and dropdowns for conditions ---
|
| 67 |
+
demo = gr.Interface(
|
| 68 |
+
fn=predict_anomaly,
|
| 69 |
+
inputs=[
|
| 70 |
+
gr.Slider(800, 5000, value=2500, label="Engine RPM"),
|
| 71 |
+
gr.Slider(25, 40, value=32, label="Ambient Temperature (°C)"),
|
| 72 |
+
gr.Slider(0, 100, value=75, label="Fuel Level (%)"),
|
| 73 |
+
gr.Dropdown(["Calm", "Choppy", "Stormy"], value="Calm", label="Current Sea State"),
|
| 74 |
+
gr.Number(label="Actual Measured Vibration (from your sensor)")
|
| 75 |
+
],
|
| 76 |
+
outputs=[
|
| 77 |
+
gr.Textbox(label="Prediction Status"),
|
| 78 |
+
gr.Textbox(label="Analysis Details")
|
| 79 |
+
],
|
| 80 |
+
title="Zodiac 1.0 Advanced Engine Diagnostics",
|
| 81 |
+
description=description,
|
| 82 |
+
theme=gr.themes.Soft()
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
if __name__ == "__main__":
|
| 86 |
+
if model is not None:
|
| 87 |
+
demo.launch()
|
| 88 |
+
else:
|
| 89 |
+
print("Gradio app will not launch because the model could not be loaded.")
|
data/raw_data.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data_logger.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- data_logger.py ---
|
| 2 |
+
from flask import Flask, request
|
| 3 |
+
import pandas as pd
|
| 4 |
+
import os
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
|
| 7 |
+
app = Flask(__name__)
|
| 8 |
+
|
| 9 |
+
# Define the data directory and the full path for the CSV file
|
| 10 |
+
DATA_DIR = 'data'
|
| 11 |
+
CSV_FILE = os.path.join(DATA_DIR, 'raw_data.csv')
|
| 12 |
+
|
| 13 |
+
@app.route('/data', methods=['POST'])
|
| 14 |
+
def receive_data():
|
| 15 |
+
try:
|
| 16 |
+
# Ensure the data directory exists
|
| 17 |
+
os.makedirs(DATA_DIR, exist_ok=True)
|
| 18 |
+
|
| 19 |
+
data = request.get_json()
|
| 20 |
+
log_data = {
|
| 21 |
+
'timestamp': [datetime.now().strftime('%Y-%m-%d %H:%M:%S')],
|
| 22 |
+
'ax': [data.get('ax')], 'ay': [data.get('ay')], 'az': [data.get('az')],
|
| 23 |
+
'gx': [data.get('gx')], 'gy': [data.get('gy')], 'gz': [data.get('gz')],
|
| 24 |
+
'temperature_c': [data.get('temperature_c')]
|
| 25 |
+
}
|
| 26 |
+
df_new = pd.DataFrame(log_data)
|
| 27 |
+
|
| 28 |
+
if not os.path.exists(CSV_FILE):
|
| 29 |
+
df_new.to_csv(CSV_FILE, index=False, header=True)
|
| 30 |
+
else:
|
| 31 |
+
df_new.to_csv(CSV_FILE, mode='a', index=False, header=False)
|
| 32 |
+
return "Data received successfully", 200
|
| 33 |
+
except Exception as e:
|
| 34 |
+
return "Error", 400
|
| 35 |
+
|
| 36 |
+
if __name__ == '__main__':
|
| 37 |
+
app.run(host='0.0.0.0', port=5000, debug=True)
|
generate_data.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- generate_data.py (Regression Version) ---
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
from datetime import datetime, timedelta
|
| 6 |
+
|
| 7 |
+
def generate_data(filename="raw_data.csv", total_rows=10000):
|
| 8 |
+
"""
|
| 9 |
+
Generates a dataset where vibration is a function of multiple operating conditions.
|
| 10 |
+
"""
|
| 11 |
+
data_dir = 'data'
|
| 12 |
+
os.makedirs(data_dir, exist_ok=True)
|
| 13 |
+
filepath = os.path.join(data_dir, filename)
|
| 14 |
+
|
| 15 |
+
print(f"Generating regression dataset with {total_rows} rows...")
|
| 16 |
+
|
| 17 |
+
# Simulate different conditions
|
| 18 |
+
rpms = np.random.randint(800, 4500, total_rows)
|
| 19 |
+
ambient_temps = np.random.uniform(28, 35, total_rows) # Tropical temps
|
| 20 |
+
fuel_levels = np.random.uniform(10, 100, total_rows) # Percentage
|
| 21 |
+
# 0=Calm, 1=Choppy, 2=Stormy
|
| 22 |
+
sea_states = np.random.choice([0, 1, 2], total_rows, p=[0.6, 0.3, 0.1])
|
| 23 |
+
|
| 24 |
+
# --- The Core Logic: Vibration is a function of conditions ---
|
| 25 |
+
# Base vibration increases with the square of RPM
|
| 26 |
+
base_vibration = 9.8 + (rpms / 1000)**2
|
| 27 |
+
|
| 28 |
+
# Temperature adds a small linear effect
|
| 29 |
+
temp_effect = (ambient_temps - 28) * 0.05
|
| 30 |
+
|
| 31 |
+
# Sea state adds random noise
|
| 32 |
+
sea_effect = np.random.normal(0, sea_states * 0.2)
|
| 33 |
+
|
| 34 |
+
# A developing fault adds a slow, growing trend over time
|
| 35 |
+
fault_trend = np.linspace(0, 1, total_rows) * 3.0
|
| 36 |
+
|
| 37 |
+
# Combine all effects to get the final measured vibration
|
| 38 |
+
az_vibration = base_vibration + temp_effect + sea_effect + fault_trend
|
| 39 |
+
|
| 40 |
+
df = pd.DataFrame({
|
| 41 |
+
'rpm': rpms,
|
| 42 |
+
'ambient_temp_c': ambient_temps,
|
| 43 |
+
'fuel_level_percent': fuel_levels,
|
| 44 |
+
'sea_state': sea_states,
|
| 45 |
+
'az_vibration_actual': az_vibration
|
| 46 |
+
})
|
| 47 |
+
|
| 48 |
+
df.to_csv(filepath, index=False)
|
| 49 |
+
print(f"Successfully generated and saved data to '{filepath}'")
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
generate_data()
|
hardware/esp32_sensor_code.ino
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// --- hardware/esp32_sensor_code.ino ---
|
| 2 |
+
#include <WiFi.h>
|
| 3 |
+
#include <HTTPClient.h>
|
| 4 |
+
#include <Wire.h>
|
| 5 |
+
#include <Adafruit_MPU6050.h>
|
| 6 |
+
#include <Adafruit_Sensor.h>
|
| 7 |
+
#include <OneWire.h>
|
| 8 |
+
#include <DallasTemperature.h>
|
| 9 |
+
|
| 10 |
+
// --- Configuration ---
|
| 11 |
+
const char* ssid = "YOUR_WIFI_SSID";
|
| 12 |
+
const char* password = "YOUR_WIFI_PASSWORD";
|
| 13 |
+
String serverName = "https://your-codespace-url-5000.app.github.dev/data";
|
| 14 |
+
#define ONEWIRE_BUS 4
|
| 15 |
+
|
| 16 |
+
// --- Global Objects ---
|
| 17 |
+
Adafruit_MPU6050 mpu;
|
| 18 |
+
OneWire oneWire(ONEWIRE_BUS);
|
| 19 |
+
DallasTemperature sensors(&oneWire);
|
| 20 |
+
|
| 21 |
+
void connectToWiFi() { /* ... connection logic ... */ }
|
| 22 |
+
void setup() { /* ... sensor and wifi init ... */ }
|
| 23 |
+
|
| 24 |
+
void loop() {
|
| 25 |
+
if(WiFi.status() == WL_CONNECTED) {
|
| 26 |
+
HTTPClient http;
|
| 27 |
+
sensors_event_t a, g, temp_event;
|
| 28 |
+
mpu.getEvent(&a, &g, &temp_event);
|
| 29 |
+
sensors.requestTemperatures();
|
| 30 |
+
float temperatureC = sensors.getTempCByIndex(0);
|
| 31 |
+
|
| 32 |
+
String jsonPayload = "{\"ax\":" + String(a.acceleration.x) + ",\"ay\":" + String(a.acceleration.y) + ",\"az\":" + String(a.acceleration.z) + ",\"gx\":" + String(g.gyro.x) + ",\"gy\":" + String(g.gyro.y) + ",\"gz\":" + String(g.gyro.z) + ",\"temperature_c\":" + String(temperatureC) + "}";
|
| 33 |
+
|
| 34 |
+
http.begin(serverName);
|
| 35 |
+
http.addHeader("Content-Type", "application/json");
|
| 36 |
+
http.POST(jsonPayload);
|
| 37 |
+
http.end();
|
| 38 |
+
}
|
| 39 |
+
delay(2000);
|
| 40 |
+
}
|
models/regression_anomaly_threshold.npy
ADDED
|
Binary file (136 Bytes). View file
|
|
|
models/vibration_regressor.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pandas
|
| 2 |
+
numpy
|
| 3 |
+
scikit-learn
|
| 4 |
+
gradio
|
| 5 |
+
xgboost
|
train_model.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --- train_model.py (Regression Version) ---
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
import xgboost as xgb
|
| 6 |
+
from sklearn.model_selection import train_test_split
|
| 7 |
+
|
| 8 |
+
def main():
|
| 9 |
+
"""Trains an XGBoost model to predict normal vibration."""
|
| 10 |
+
print("--- Starting Regression Model Training ---")
|
| 11 |
+
|
| 12 |
+
data_path = os.path.join('data', 'raw_data.csv')
|
| 13 |
+
if not os.path.exists(data_path):
|
| 14 |
+
print(f"Error: {data_path} not found. Please run generate_data.py")
|
| 15 |
+
return
|
| 16 |
+
|
| 17 |
+
df = pd.read_csv(data_path)
|
| 18 |
+
df.dropna(inplace=True)
|
| 19 |
+
|
| 20 |
+
# --- Feature & Target Definition ---
|
| 21 |
+
# The model will learn to predict the 'actual' vibration based on the conditions
|
| 22 |
+
features = ['rpm', 'ambient_temp_c', 'fuel_level_percent', 'sea_state']
|
| 23 |
+
target = 'az_vibration_actual'
|
| 24 |
+
|
| 25 |
+
X = df[features]
|
| 26 |
+
y = df[target]
|
| 27 |
+
|
| 28 |
+
# --- Model Training ---
|
| 29 |
+
# We will train on the 'healthy' portion of the data (first 60%)
|
| 30 |
+
# This teaches the model what "normal" looks like before the fault gets bad
|
| 31 |
+
healthy_cutoff = int(len(df) * 0.6)
|
| 32 |
+
X_train = X.iloc[:healthy_cutoff]
|
| 33 |
+
y_train = y.iloc[:healthy_cutoff]
|
| 34 |
+
|
| 35 |
+
# Initialize and train the XGBoost Regressor
|
| 36 |
+
model = xgb.XGBRegressor(
|
| 37 |
+
objective='reg:squarederror',
|
| 38 |
+
n_estimators=1000,
|
| 39 |
+
learning_rate=0.05,
|
| 40 |
+
max_depth=5,
|
| 41 |
+
subsample=0.8,
|
| 42 |
+
colsample_bytree=0.8,
|
| 43 |
+
random_state=42,
|
| 44 |
+
n_jobs=-1 # Use all available CPU cores
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
print("\nStarting model training...")
|
| 48 |
+
model.fit(X_train, y_train, verbose=False)
|
| 49 |
+
|
| 50 |
+
# --- Save Model and Calculate Anomaly Threshold ---
|
| 51 |
+
model_dir = 'models'
|
| 52 |
+
os.makedirs(model_dir, exist_ok=True)
|
| 53 |
+
model.save_model(os.path.join(model_dir, 'vibration_regressor.json'))
|
| 54 |
+
print(f"\nModel saved to: {model_dir}/vibration_regressor.json")
|
| 55 |
+
|
| 56 |
+
# The key step: Use the trained model to predict what the vibration *should* have been
|
| 57 |
+
# for the healthy data, then find the error.
|
| 58 |
+
y_pred_healthy = model.predict(X_train)
|
| 59 |
+
errors = np.abs(y_train - y_pred_healthy)
|
| 60 |
+
|
| 61 |
+
# The anomaly threshold is a level of error that is unusual for a healthy machine
|
| 62 |
+
anomaly_threshold = np.percentile(errors, 99) * 1.5 # 99th percentile, with a buffer
|
| 63 |
+
|
| 64 |
+
np.save(os.path.join(model_dir, 'regression_anomaly_threshold.npy'), anomaly_threshold)
|
| 65 |
+
print(f"Anomaly threshold ({anomaly_threshold:.4f}) saved to: {model_dir}/regression_anomaly_threshold.npy")
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|