Категории

Умная GPS-охрана для авто на ESP32 + SIM800L. Как у топовых сигналок, но из AliExpress

2025-10-16 12:48:28 | Статья из категории: Безопасность
Умная GPS-охрана для авто на ESP32 + SIM800L

Хочешь знать, где твоя машина, даже если её угнали? Не веришь штатной сигнализации? Собери свою IoT-систему охраны — как у топовых брендов, но за 2000 рублей из китайских модулей. Работает даже после отключения аккумулятора, ловит присутствие хозяина по телефону и шлёт всё в твою самописную систему на PHP.

Логические схемы событий: как система принимает решения

Все события делятся на уровни подозрительности. Тревога срабатывает только если отсутствует хозяин (BLE-метка не обнаружена). Ниже — полный перечень сценариев и условий их срабатывания.

1. Разбитие стекла
IF (BME280.pressure_change_rate > 0.8 гПа/сек) AND (KY-038.sound_level > 800)
→ Событие: glass_break
2. Присутствие человека в салоне
IF (HM-10.BLE_device == "MyPhone") AND (TTP223.seat_occupied == HIGH)
→ Режим: owner_present (все тревоги отключены)
3. Кто-то долго крутится у машины
IF (RCWL-0516.motion == HIGH) for > 15 сек AND (owner_present == false)
→ Событие: suspicious_loitering
4. Громкий звук (взлом, удар)
IF (KY-038.sound_level > 900) AND (SW-420.vibration == HIGH)
→ Событие: loud_noise
5. Резкое изменение давления (открытие двери)
IF (BME280.pressure_change_rate > 0.5 гПа/сек) AND (no BLE)
→ Событие: door_opened
6. Перемещение / эвакуация
IF (MPU6050.acceleration.magnitude > 0.3g) for > 5 сек AND (GPS.position_changed == true)
→ Событие: vehicle_moving
7. Ускорение (машина поехала)
IF (MPU6050.accel_x > 0.5g) AND (BME280.pressure stable) AND (no BLE)
→ Событие: engine_started
8. Включение зажигания
IF (car_voltage > 13.2 В) AND (no BLE)
→ Событие: ignition_on
9. Отключение бортового питания (подозрительно!)
IF (car_voltage < 11.0 В) AND (system_powered_by_18650 == true)
→ Событие: power_disconnected (угон в процессе!)
10. Вибрация без движения (удар, поддомкрачивание)
IF (SW-420.vibration == HIGH) AND (MPU6050.no_acceleration) AND (no BLE)
→ Событие: tampering

Общее правило для всех событий:

IF (owner_present == true) → IGNORE ALL EVENTS
ELSE → LOG + SEND TO SERVER

Система не принимает решений — она фиксирует факты. Ты сам видишь: «в 03:14 напряжение пропало, через 10 сек — движение, координаты такие-то». Решать — угон или нет — будешь ты.

Идея системы

Классика умных сигналок:

Мы реализуем это на ESP32 (а не на Arduino — он мощнее, есть Bluetooth, Wi-Fi, два ядра и встроенный BLE).

Что понадобится

Важно! SIM800L питается от 4.2 В и жрёт до 2 А в пике. Подключай его к отдельному выходу 18650 через MOSFET или DC-DC. Не от ESP32!

Как это работает

  1. ESP32 каждые 10 сек сканирует BLE — ищет твой телефон (по MAC или имени).
  2. Если телефон рядом — режим «спячка»: датчики не читаются.
  3. Если телефона нет 5 минут — включается «режим охраны».
  4. В режиме охраны:
    • Читаем напряжение бортсети (через делитель)
    • Слушаем вибрацию, звук, давление, движение
    • Если что-то сработало — включаем GPS, получаем координаты
    • Отправляем JSON на твой PHP-сервер

Библиотеки для ESP32

Установи в Arduino IDE через менеджер библиотек:

Код: отправка данных на PHP

Пример функции отправки события:

#include <HTTPClient.h>

void sendEventToServer(String eventType, float lat, float lon, float voltage) {
  if (WiFi.status() == WL_CONNECTED) { // или через GPRS, если нет Wi-Fi
    HTTPClient http;
    http.begin("http://yourdomain.com/tracker.php");
    http.addHeader("Content-Type", "application/json");

    String json = "{";
    json += "\"event\":\"" + eventType + "\",";
    json += "\"lat\":" + String(lat, 6) + ",";
    json += "\"lon\":" + String(lon, 6) + ",";
    json += "\"voltage\":" + String(voltage) + ",";
    json += "\"time\":\"" + getTimeString() + "\"";
    json += "}";

    int httpResponseCode = http.POST(json);
    http.end();
  }
}

PHP-сервер: приём и уведомления

Файл tracker.php:

<?php
$data = json_decode(file_get_contents('php://input'), true);

// Сохраняем в БД
file_put_contents('logs.txt', date('Y-m-d H:i:s') . " " . json_encode($data) . "\n", FILE_APPEND);

// Отправляем в Telegram
$botToken = '123456:ABC';
$chatId = '7890';
$msg = "🚨 Тревога: {$data['event']}\n📍 {$data['lat']}, {$data['lon']}\n🔋 {$data['voltage']} В";

file_get_contents("https://api.telegram.org/bot{$botToken}/sendMessage?chat_id={$chatId}&text=" . urlencode($msg));
?>

Логика событий

Твой PHP-сервер (или отдельная IoT-система по Webhook/WeGuard) может:

Безопасность: добавь в PHP проверку токена и IP, чтобы не слал любой желающий.

Итог

Ты получил:

И всё это — за цену дешёвой китайской сигналки, но с полным контролем и расширяемостью.

Автор: IoT-энтузиаст | Октябрь 2025 | Собрано на коленке, работает на Жигулях

⚠️ Вмешательство в электрику авто — на свой страх и риск. Не используй для незаконных действий.

Тестовый код, пока даже не смотрел и не загружал


#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>

// === BLE ===
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

// === GPS ===
#include <Adafruit_GPS.h>
#define GPSSerial Serial2
Adafruit_GPS GPS(&GPSSerial);

// === BME280 ===
#include <Adafruit_BME280.h>
Adafruit_BME280 bme;

// === MPU6050 ===
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
Adafruit_MPU6050 mpu;

// === GSM ===
#include <TinyGsmClient.h>
#define TINY_GSM_MODEM_SIM800
#define SerialAT Serial1
TinyGsm modem(SerialAT);
TinyGsmClient client(modem);
const char* apn = "internet"; // уточни у оператора

// === Пины ===
#define VOLTAGE_PIN   34    // аналоговый вход (делитель 12В)
#define VIBRATION_PIN 4     // SW-420
#define RADAR_PIN     15    // RCWL-0516

// === BLE ===
bool phoneDetected = false;
String targetDeviceName = "YourPhoneName"; // ← ЗАМЕНИ НА ИМЯ ТВОЕГО ТЕЛЕФОНА!

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    if (advertisedDevice.haveName()) {
      if (advertisedDevice.getName().c_str() == targetDeviceName) {
        phoneDetected = true;
      }
    }
  }
};

// === Вспомогательные функции ===
float readCarVoltage() {
  int val = analogRead(VOLTAGE_PIN);
  // Делитель: 20к + 10к → коэффициент 3.0
  return (val * 3.3 / 4095.0) * 3.0;
}

void sendEvent(const char* eventType) {
  // Включаем SIM800L (если управляешь через пин — добавь digitalWrite)
  delay(2000);

  SerialAT.begin(9600, SERIAL_8N1, 16, 17);
  modem.restart();
  if (!modem.gprsConnect(apn)) {
    Serial.println("GPRS failed");
    return;
  }

  // Ждём GPS фикс
  GPS.begin(9600);
  bool gpsFix = false;
  for (int i = 0; i < 20; i++) {
    GPS.read();
    if (GPS.newNMEAreceived()) {
      if (GPS.parse(GPS.lastNMEA())) {
        if (GPS.fix) {
          gpsFix = true;
          break;
        }
      }
    }
    delay(500);
  }

  // Собираем данные
  String json = "{";
  json += "\"event\":\"" + String(eventType) + "\",";
  json += "\"voltage\":" + String(readCarVoltage()) + ",";
  json += "\"vibration\":" + String(digitalRead(VIBRATION_PIN)) + ",";
  json += "\"radar\":" + String(digitalRead(RADAR_PIN)) + ",";

  if (gpsFix) {
    json += "\"lat\":" + String(GPS.latitude, 6) + ",";
    json += "\"lon\":" + String(GPS.longitude, 6) + ",";
  } else {
    json += "\"lat\":0,\"lon\":0,";
  }

  if (bme.performReading()) {
    json += "\"pressure\":" + String(bme.pressure / 100.0) + ",";
    json += "\"temp\":" + String(bme.temperature) + ",";
  } else {
    json += "\"pressure\":0,\"temp\":0,";
  }

  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  json += "\"accel_x\":" + String(a.acceleration.x) + "";
  json += "}";

  // Отправка на сервер
  if (client.connect("yourdomain.com", 80)) {
    client.print("POST /tracker.php HTTP/1.1\r\n");
    client.print("Host: yourdomain.com\r\n");
    client.print("Content-Type: application/json\r\n");
    client.print("Content-Length: " + String(json.length()) + "\r\n");
    client.print("\r\n");
    client.print(json);
    client.stop();
    Serial.println("Sent: " + json);
  }

  modem.gprsDisconnect();
  GPS.end();
}

// === SETUP ===
void setup() {
  Serial.begin(115200);
  Wire.begin();

  // Датчики
  pinMode(VIBRATION_PIN, INPUT);
  pinMode(RADAR_PIN, INPUT);
  analogReadResolution(12); // ESP32

  // GPS
  GPSSerial.begin(9600, SERIAL_8N1, 5, -1); // RX=5, TX не нужен

  // BME280
  if (!bme.begin(0x76)) {
    Serial.println("BME280 not found");
  }

  // MPU6050
  if (!mpu.begin()) {
    Serial.println("MPU6050 not found");
  }
  mpu.setAccelerometerRange(MPU6050_RANGE_8_G);

  // GSM
  SerialAT.begin(9600, SERIAL_8N1, 16, 17);
  modem.restart();

  // BLE
  BLEDevice::init("");
}

// === LOOP ===
void loop() {
  // Сканируем BLE 5 сек
  phoneDetected = false;
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
  pBLEScan->clearResults();

  delay(1000);

  // Если телефона нет — читаем датчики
  if (!phoneDetected) {
    Serial.println("Phone NOT detected. Reading sensors...");

    bool eventTriggered = false;
    String eventType = "";

    // Проверка вибрации
    if (digitalRead(VIBRATION_PIN) == HIGH) {
      eventType = "vibration";
      eventTriggered = true;
    }

    // Проверка радара
    if (digitalRead(RADAR_PIN) == HIGH) {
      eventType = "radar_motion";
      eventTriggered = true;
    }

    // Проверка напряжения (зажигание)
    float v = readCarVoltage();
    if (v > 13.2) {
      eventType = "ignition_on";
      eventTriggered = true;
    }

    if (eventTriggered) {
      sendEvent(eventType.c_str());
      delay(10000); // анти-спам
    }
  } else {
    Serial.println("Phone detected. Sleeping...");
  }

  // Спим 30 сек
  delay(30000);
}

подключение

SIM800L TX - GPIO 17
SIM800L RX - GPIO 16
NEO-6M TX - GPIO 5
BME280 SDA - GPIO 21
BME280 SCL - GPIO 22
MPU6050 SDA - GPIO 21
MPU6050 SCL - GPIO 22
SW-420 - GPIO 4
RCWL-0516 - GPIO 15
Делитель 12В - GPIO 34 (аналоговый)

Немного передумал с безопасностью ...

Как я сделал умную сигнализацию для машины: безопасное «я рядом — я ушёл» без подделки

Многие пытаются использовать Bluetooth-маячки (beacon), чтобы автоматически включать/выключать сигнализацию: «подошёл — отключилась, ушёл — включилась». Но есть проблема: любой может просканировать MAC-адрес или UUID и подделать сигнал. Это делает такие системы уязвимыми к простой replay-атаке.

Я решил пойти другим путём — использовать одноразовые криптографические токены, по принципу автомобильных иммобилайзеров. Теперь моя сигнализация реагирует только на аутентичное подтверждение присутствия, а не на «крик в эфир».

Идея: Challenge-Response с одноразовым кодом

Вместо того чтобы маячок постоянно излучал один и тот же идентификатор, я сделал так:

  1. У двери машины стоит считыватель (ESP32).
  2. Я подхожу и нажимаю кнопку на брелке (или прикладываю NFC-метку).
  3. Брелок генерирует одноразовый код на основе:
    • Секретного ключа (известного только брелку и машине),
    • Внутреннего счётчика (который увеличивается после каждого использования).
  4. Машина проверяет код. Если он валиден и ещё не использовался — сигнализация отключается.
  5. Когда я ухожу — через 30 секунд без подтверждения — сигнализация включается автоматически.

Даже если злоумышленник перехватит код — он не сработает повторно. Это называется HOTP (HMAC-Based One-Time Password).

Пример: упрощённый код для ESP32 (машина)


// Упрощённая логика на стороне автомобиля (ESP32)
#include <WiFi.h>
#include <HTTPClient.h>
#include <Crypto.h> // Библиотека для HMAC, например: https://github.com/intrbiz/arduino-crypto

const uint8_t SECRET_KEY[32] = { /* твой 256-битный секрет */ };
uint32_t last_counter = 1000; // последний принятый счётчик

bool verify_hotp(const char* received_code, uint32_t counter) {
  byte hmac[32];
  hmac_sha256(SECRET_KEY, 32, (byte*)&amp;counter, 4, hmac);
  
  // Преобразуем HMAC в 6-значный код (как в Google Authenticator)
  uint32_t code = ((hmac[19] &amp; 0x7F) << 24 | 
                   (hmac[20] &amp; 0xFF) << 16 | 
                   (hmac[21] &amp; 0xFF) << 8  | 
                   (hmac[22] &amp; 0xFF)) % 1000000;
  
  return (atoi(received_code) == code) &amp;&amp; (counter > last_counter);
}

void handle_unlock_request(const char* hotp_code, uint32_t counter) {
  if (verify_hotp(hotp_code, counter)) {
    last_counter = counter;
    digitalWrite(RELAY_PIN, LOW); // отключаем сигнализацию
    Serial.println("✅ Сигнализация ОТКЛЮЧЕНА");
    
    // Запускаем таймер на включение при уходе
    startAutoArmTimer(30); // 30 секунд
  } else {
    Serial.println("❌ Неверный или повторный код!");
  }
}

На стороне брелка (например, другой ESP32 или nRF52)

;
// При нажатии кнопки
uint32_t counter = readCounterFromEEPROM(); // читаем текущий счётчик
counter++;

// Генерируем HOTP
byte hmac[32];
hmac_sha256(SECRET_KEY, 32, (byte*)&amp;counter, 4, hmac);
uint32_t code = /* ... как выше ... */ % 1000000;

// Отправляем по BLE / LoRa / GSM / MQTT
sendToCar(String(code), counter);

// Сохраняем новый счётчик
saveCounterToEEPROM(counter);

Почему это безопасно?

💡 Совет: для максимальной безопасности используй микросхему с Secure Element (например, ATECC608B или NTAG 424 DNA). Там ключ физически недоступен даже при вскрытии устройства.

Теперь моя машина знает: если код принят — это точно я, а не кто-то с клоном маячка. А когда я ухожу — она сама встаёт на охрану. Без облаков, без уязвимостей, без «водить телефоном у двери».

Комментарии

Пока нет комментариев. Будьте первым!

Оставить комментарий

← Назад к списку статей

Важно: Блог-эксперимент

Блог только запустил, все статьи генерирую через нейросеть т.к. лень, возможны ошибки. Просто чтобы вы знали и не запускали ядерный реактор по моим статьям ))
Если у вас есть вопросы, или Нашли неточность? пишите в коментах — вместе поправим и сделаем статью более качественной. Я лично объясню нюансы из практики.

Посетителей сегодня: 0


Кто я | Контакты и регион

© Digital Specialist | Не являемся сотрудниками Google, Яндекса и NASA