#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <MPU6050_light.h>
#include <HCSR04.h>
MPU6050 mpu(Wire);
UltraSonicDistanceSensor distanceSensor(25, 26); // TRIG=25, ECHO=26
// --- WiFi ---
const char* SSID = "Wokwi-GUEST";
const char* PASSWORD = "";
// --- WebServer ---
WebServer server(80);
// --- Піни ---
const int BUZZER_PIN = 27;
// --- Класи станів велосипеда ---
const char* CLASSES[] = {"idle", "approaching_obstacle", "high_speed", "critical_danger"};
const int NUM_CLASSES = 4;
const int INPUT_SIZE = 8; // 7 з MPU + 1 з HC-SR04
// --- Параметри нейронки ---
float W[NUM_CLASSES][INPUT_SIZE];
float b[NUM_CLASSES];
float lr = 0.01;
int epochs = 15;
int samplesPerClass = 200;
float epochLoss[50];
// --- Генератор псевдоданих ---
void generateSample(int label, float* x) {
for (int i = 0; i < 7; i++) x[i] = random(-100, 100) / 100.0;
if (label == 0) x[7] = random(60, 200) / 10.0; // idle - стоїть, далеко
else if (label == 1) x[7] = random(10, 40) / 10.0; // obstacle close
else if (label == 2) x[7] = random(50, 150) / 10.0; // fast
else x[7] = random(5, 15) / 10.0; // critical
}
// --- Softmax ---
float softmax(float* z, int i) {
float sum = 0;
for (int j = 0; j < NUM_CLASSES; j++) sum += exp(z[j]);
return exp(z[i]) / sum;
}
// --- Передбачення ---
int predict(float* x) {
float z[NUM_CLASSES];
for (int i = 0; i < NUM_CLASSES; i++) {
z[i] = b[i];
for (int j = 0; j < INPUT_SIZE; j++) z[i] += W[i][j] * x[j];
}
int best = 0;
float bestVal = -1e9;
for (int i = 0; i < NUM_CLASSES; i++) {
float p = softmax(z, i);
if (p > bestVal) {
bestVal = p;
best = i;
}
}
return best;
}
// --- Крок навчання ---
float trainStep(float* x, int yTrue) {
float z[NUM_CLASSES];
float p[NUM_CLASSES];
for (int i = 0; i < NUM_CLASSES; i++) {
z[i] = b[i];
for (int j = 0; j < INPUT_SIZE; j++) z[i] += W[i][j] * x[j];
}
for (int i = 0; i < NUM_CLASSES; i++) p[i] = softmax(z, i);
float loss = 0;
for (int i = 0; i < NUM_CLASSES; i++) {
float target = (i == yTrue ? 1.0 : 0.0);
loss += -target * log(max(p[i], (float)1e-6));
float grad = p[i] - target;
for (int j = 0; j < INPUT_SIZE; j++) W[i][j] -= lr * grad * x[j];
b[i] -= lr * grad;
}
return loss;
}
// --- HTML сторінка ---
const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head><title>Bike Safety Monitor</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script></head>
<body style="font-family:sans-serif;text-align:center;">
<h2>Training Loss per Epoch</h2>
<canvas id="lossChart"></canvas>
<script>
async function fetchData() {
const res = await fetch('/data');
const d = await res.json();
new Chart(document.getElementById('lossChart'), {
type: 'line',
data: {
labels: d.epochs,
datasets: [{ label: 'Loss', data: d.loss, borderColor: 'blue', fill: false }]
},
options: { scales: { y: { beginAtZero: true } } }
});
}
fetchData();
</script>
</body></html>
)rawliteral";
void handleRoot() { server.send(200, "text/html", MAIN_page); }
void handleData() {
String json = "{ \"epochs\": [";
for (int i = 0; i < epochs; i++) {
json += String(i + 1);
if (i < epochs - 1) json += ",";
}
json += "], \"loss\": [";
for (int i = 0; i < epochs; i++) {
json += String(epochLoss[i], 4);
if (i < epochs - 1) json += ",";
}
json += "] }";
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
// WiFi
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected!");
Serial.println(WiFi.localIP());
// Web server
server.on("/", handleRoot);
server.on("/data", handleData);
server.begin();
// MPU6050
Wire.begin();
mpu.begin();
mpu.calcOffsets(true, true);
// Ініціалізація ваг
for (int i = 0; i < NUM_CLASSES; i++) {
b[i] = 0;
for (int j = 0; j < INPUT_SIZE; j++) {
W[i][j] = ((float)random(-100, 100)) / 1000.0;
}
}
// Навчання
Serial.println("Навчання моделі безпеки велосипеда...");
for (int e = 0; e < epochs; e++) {
float totalLoss = 0;
for (int c = 0; c < NUM_CLASSES; c++) {
for (int n = 0; n < samplesPerClass; n++) {
float x[INPUT_SIZE];
generateSample(c, x);
totalLoss += trainStep(x, c);
}
}
epochLoss[e] = totalLoss / (NUM_CLASSES * samplesPerClass);
Serial.print("Epoch "); Serial.print(e + 1);
Serial.print(" Loss: "); Serial.println(epochLoss[e], 4);
}
}
void loop() {
server.handleClient();
mpu.update();
float distance = distanceSensor.measureDistanceCm();
if (distance <= 0 || distance > 400) distance = 400;
float x[INPUT_SIZE] = {
mpu.getAccX(), mpu.getAccY(), mpu.getAccZ(),
mpu.getGyroX(), mpu.getGyroY(), mpu.getGyroZ(),
random(0, 100) / 100.0f,
distance / 100.0f
};
int pred = predict(x);
Serial.print("Bike state: ");
Serial.println(CLASSES[pred]);
// --- Реакції ---
switch (pred) {
case 0: // idle
noTone(BUZZER_PIN);
break;
case 1: // obstacle close
tone(BUZZER_PIN, 900, 200);
delay(200);
break;
case 2: // high speed
tone(BUZZER_PIN, 1200, 150);
delay(150);
break;
case 3: // critical danger
tone(BUZZER_PIN, 2000, 600);
delay(600);
break;
}
delay(400);
}