Умная система управления светофором с реальными данными
Компоненты и расходники
| Arduino Yun | × | 1 |
| Arduino UNO (или любая другая плата Arduino) | × | 1 |
| Ультразвуковой датчик HC-SR04 | × | 1 |
| DS3231 RTC (часы реального времени) | × | 1 |
| Светодиоды (1 красный, 1 жёлтый, 1 зелёный) | × | 3 |
| Резисторы 330 Ом | × | 3 |
| Модули 433 МГц (передатчик + приёмник) | × | 1 |
| Android-устройство | × | 1 |
| SD-карта | × | 1 |
| AllThingsTalk IOTOPIA Rapid Development kit | × | 1 |
| Перемычки | × | 1 |
Приложения и онлайн-сервисы
- Arduino IDE
- Android Studio
- Хостинг MySQL базы данных
О проекте
Введение
Пробки раздражают даже самых терпеливых водителей, и ситуация ухудшается. А что, если это можно изменить с помощью интеллектуальных светофоров? Мы создали систему информации о светофоре, которая сообщает водителю, с какой скоростью ему нужно двигаться, чтобы проехать перекрёсток на зелёном свете, не превышая допустимый лимит. И когда ожидание красного света неизбежно, система показывает, через сколько секунд загорится зелёный.

Светофор передаёт данные о длительности зелёного сигнала, времени до следующего зелёного и т.д. в онлайн-базу данных. GPS или приложение на смартфоне извлекает эти данные, вычисляет рекомендуемую скорость для проезда на зелёном и отображает обратный отсчёт оставшегося времени красного сигнала.
Пример
Вы едете в зоне 70 км/ч, соблюдая лимит. Впереди светофор красный, зелёный загорится через 30 секунд. Вы находитесь в 500 метрах от светофора, поэтому система рекомендует снизить скорость до 60 км/ч.

Демонстрация проекта
Как мы это сделали?
Для демонстрации мы собрали светофор на базе Arduino Yun. Он состоит из трёх светодиодов и часов реального времени (RTC). Сигналы переключаются с заданным интервалом. Arduino Yun сохраняет время переключения на зелёный/красный, затем запускает PHP-скрипт, который подключается к онлайн-базе MySQL и записывает данные. Android-приложение (созданное в Android Studio) получает данные из базы через другой PHP-скрипт, возвращающий JSON. Приложение вычисляет рекомендуемую скорость и отображает таймер обратного отсчёта, если проехать на зелёном невозможно.

Для расчёта скорости приложению нужно знать расстояние до светофора. Оно определяется по GPS-координатам, но точность смартфона недостаточна для малого масштаба. Поэтому мы использовали ультразвуковой датчик HC-SR04. Arduino Yun получает данные о расстоянии от второй Arduino через RF-модули 433 МГц. При каждом новом измерении база данных обновляется, и приложение может вычислить точную рекомендуемую скорость.

Инструкции
- Создайте онлайн-базу MySQL (мы использовали freemysqlhosting.net).
- Добавьте таблицу
TrafficL_dataсогласно схеме. - Соберите аппаратную часть (передатчик) по схеме «Arduino transmitter».
- Загрузите код передатчика в Arduino.
- Подключите Arduino Yun к Wi-Fi.
- Настройте Linux-сервер Arduino Yun через PuTTY:
Еслиopkg update opkg install php5-mod-mysqli opkg install php5-cli nano /mnt/sda1/MySQL_UpdateTrafficLData.phpnanoне найден:opkg install nano.chmod 755 /mnt/sda1/MySQL_UpdateTrafficLData.php /mnt/sda1/MySQL_UpdateTrafficLData.php nano /mnt/sda1/MySQL_UpdateDistance.php chmod 755 /mnt/sda1/MySQL_UpdateDistance.php /mnt/sda1/MySQL_UpdateDistance.php - Соберите приёмник на Arduino Yun по схеме.
- Загрузите код приёмника и работы с БД в Arduino Yun.
- Откройте Serial Monitor — должны появиться данные, похожие на скриншоты.
- Файлы Android Studio:
activity_main.xml,MainActivity.java(замените URL),AndroidManifest.xml,strings.xml. Также нуженEchoJSON.php. - Загрузите проект на смартфон.
Дополнительно: использование ESP8266 вместо Arduino Yun
Более дешёвый вариант — модуль на базе ESP8266 (например, NodeMCU V3).
- Создайте аккаунт на 000webhost.com.
- Создайте сайт и базу данных.
- Загрузите через File Manager файлы
CreateTable.phpиPostDemo.php, заменивusername,password,dbname. - Создайте таблицу, открыв
CreateTable.phpв браузере. - В Arduino IDE добавьте URL пакета ESP8266:
https://arduino.esp8266.com/stable/package_esp8266com_index.jsonи установите плату. - Загрузите скетч
ESP8266PostDemo.ino(обновляет БД каждые 10 секунд случайными значениями для теста). - Настройте PHP-обработчик на сервере.
- В коде приёмника замените функции
MySQL_UpdateTrafficLData()иMySQL_UpdateDistance()на работу с новым API.
Совет: тестируйте каждый компонент отдельно, прежде чем объединять их. Задавайте вопросы в комментариях с подробным описанием проблемы.
Код
- Arduino Transmitter Code
- Arduino Receiver & MySQLdb
- MySQL_UpdateTrafficLData.php
- MySQL_UpdateDistance.php
- EchoJSON.php
- MainActivity.java
- AndroidManifest.xml
- strings.xml
- activity_main.xml
Arduino Transmitter Code
/* IOTOPIA - 2016-2017 - Traffic Light Information System (Transmitter)
* by Pieter Luyten & Joppe Smeets
* Send distance to another arduino via RF 433MHz modules
* RF 433MHz module: tx_pin -> pin 3
* HC-SR04 module: trig_pin -> pin 5, echo_pin -> pin 6
*/
#define tx_pin 3
#define trig_pin 5
#define echo_pin 6
#include <VirtualWire.h>
#include "HCSR04.h"
HCSR04 ultrasonic(trig_pin, echo_pin);
int distance;
char CharMsg[21];
void setup() {
Serial.begin(9600);
vw_setup(2000);
vw_set_tx_pin(tx_pin);
}
void loop() {
distance = ultrasonic.Ranging(CM);
while (distance < 0) {
distance = ultrasonic.Ranging(CM);
}
sprintf(CharMsg, "%d,", distance);
vw_send((uint8_t *)CharMsg, strlen(CharMsg));
vw_wait_tx();
Serial.print("Distance (cm): ");
Serial.println(distance);
delay(250);
}
Arduino Receiver & MySQLdb
/* IOTOPIA - 2016-2017 - Traffic Light Information System (Receiver + Upload to MySQLdb)
* by Pieter Luyten & Joppe Smeets
* Receive distance from another arduino via RF
* Upload traffic light data and distance to MySQL database
*/
#define RedLED 5
#define OrangeLED 6
#define GreenLED 7
#define rx_pin 9
#include <VirtualWire.h>
#include <TimeAlarms.h>
#include <Wire.h>
#include "RTClib.h"
#include <Process.h>
long UnixTime_green_1, UnixTime_red_1, UnixTime_green_2, UnixTime_red_2;
long s_till_orange;
RTC_DS3231 rtc;
char StringReceived[22];
boolean i;
int interval = 5;
int distance;
void setup() {
rtc.begin();
Serial.begin(9600);
Bridge.begin();
vw_setup(2000);
vw_set_rx_pin(rx_pin);
vw_rx_start();
Alarm.timerRepeat(interval, UpdateTrafficLData);
Alarm.timerRepeat(1, PrintCurrentTime);
pinMode(GreenLED, OUTPUT);
pinMode(OrangeLED, OUTPUT);
pinMode(RedLED, OUTPUT);
digitalWrite(GreenLED, LOW);
digitalWrite(OrangeLED, LOW);
digitalWrite(RedLED, LOW);
Serial.println("Setup finished");
}
void loop() {
Alarm.delay(0);
RF_Listen();
}
void RF_Listen() {
uint8_t buf[VW_MAX_MESSAGE_LEN];
uint8_t buflen = VW_MAX_MESSAGE_LEN;
if (vw_get_message(buf, &buflen)) {
for (int a = 0; a < buflen; a++) {
StringReceived[a] = char(buf[a]);
}
sscanf(StringReceived, "%d", &distance);
MySQL_UpdateDistance();
Serial.print("Distance: ");
Serial.println(distance);
}
memset(StringReceived, 0, sizeof(StringReceived));
}
void UpdateTrafficLData() {
DateTime now = rtc.now();
if (i == 1) {
UnixTime_green_1 = now.unixtime() + interval + 1;
UnixTime_red_1 = now.unixtime() + 2 * interval + 1;
UnixTime_green_2 = now.unixtime() + 3 * interval + 1;
UnixTime_red_2 = now.unixtime() + 4 * interval + 1;
MySQL_UpdateTrafficLData();
i = 0;
} else {
UnixTime_red_1 = now.unixtime() + interval + 1;
UnixTime_green_1 = now.unixtime() + (2 * interval) + 1;
UnixTime_red_2 = now.unixtime() + 3 * interval + 1;
UnixTime_green_2 = now.unixtime() + 4 * interval + 1;
MySQL_UpdateTrafficLData();
i = 1;
}
Serial.print(" Green_1 at "); Serial.print(UnixTime_green_1);
Serial.print(" Red_1 at "); Serial.print(UnixTime_red_1);
Serial.print(" Green_2 at "); Serial.print(UnixTime_green_2);
Serial.print(" Red_2 at "); Serial.println(UnixTime_red_2);
}
void MySQL_UpdateTrafficLData() {
long dataArray[4] = {UnixTime_green_1, UnixTime_red_1, UnixTime_green_2, UnixTime_red_2};
Process p;
p.begin("/mnt/sda1/MySQL_UpdateTrafficLData.php");
for (int b = 0; b < 4; b++) p.addParameter(String(dataArray[b]));
p.run();
while (p.available() > 0) { char c = p.read(); Serial.print(c); }
Serial.flush();
}
void MySQL_UpdateDistance() {
Process p;
p.begin("/mnt/sda1/MySQL_UpdateDistance.php");
p.addParameter(String(distance));
p.run();
while (p.available() > 0) { char c = p.read(); Serial.print(c); }
Serial.flush();
}
void PrintCurrentTime() {
DateTime now = rtc.now();
SetLEDsTrafficL();
Serial.print(now.hour(), DEC); Serial.print(':');
Serial.print(now.minute(), DEC); Serial.print(':');
Serial.print(now.second(), DEC); Serial.print(" ");
Serial.println(now.unixtime());
}
void SetLEDsTrafficL() {
DateTime now = rtc.now();
s_till_orange = UnixTime_red_1 - now.unixtime();
if (i == 0) {
digitalWrite(GreenLED, LOW);
digitalWrite(OrangeLED, LOW);
digitalWrite(RedLED, HIGH);
}
if (i == 1 && s_till_orange <= 3) {
digitalWrite(GreenLED, LOW);
digitalWrite(OrangeLED, HIGH);
digitalWrite(RedLED, LOW);
}
if (i == 1 && s_till_orange > 3) {
digitalWrite(GreenLED, HIGH);
digitalWrite(OrangeLED, LOW);
digitalWrite(RedLED, LOW);
}
}
MySQL_UpdateTrafficLData.php
#!/usr/bin/php-cli
<?php
$UnixTime_green_1 = $argv[1];
$UnixTime_red_1 = $argv[2];
$UnixTime_green_2 = $argv[3];
$UnixTime_red_2 = $argv[4];
$DBServer = 'yourServerName';
$DBUser = 'yourUserName';
$DBPass = 'yourPassword';
$DBName = 'yourDBName';
$conn = new mysqli($DBServer, $DBUser, $DBPass, $DBName);
if ($conn->connect_error) {
trigger_error('Database connection failed: ' . $conn->connect_error, E_USER_ERROR);
}
$sql = "UPDATE TrafficL_data SET UnixTime_green_1='$UnixTime_green_1', UnixTime_red_1='$UnixTime_red_1', UnixTime_green_2='$UnixTime_green_2', UnixTime_red_2='$UnixTime_red_2' WHERE id=1";
if ($conn->query($sql) === false) {
trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->error, E_USER_ERROR);
} else {
echo "Data inserted!\n";
}
?>
MySQL_UpdateDistance.php
#!/usr/bin/php-cli
<?php
$Distance = $argv[1];
$DBServer = 'yourServerName';
$DBUser = 'yourUserName';
$DBPass = 'yourPassword';
$DBName = 'yourDBName';
$conn = new mysqli($DBServer, $DBUser, $DBPass, $DBName);
if ($conn->connect_error) {
trigger_error('Database connection failed: ' . $conn->connect_error, E_USER_ERROR);
}
$sql = "UPDATE TrafficL_data SET Distance='$Distance' WHERE id=1";
if ($conn->query($sql) === false) {
trigger_error('Wrong SQL: ' . $sql . ' Error: ' . $conn->error, E_USER_ERROR);
} else {
echo "Distance inserted!\n";
}
?>
EchoJSON.php
<?php
define('HOST', 'yourServerName');
define('USER', 'yourUserName');
define('PASS', 'yourPassword');
define('DB', 'yourDBName');
$con = mysqli_connect(HOST, USER, PASS, DB);
$sql = "select * from TrafficL_data";
$res = mysqli_query($con, $sql);
while ($row = mysqli_fetch_array($res, MYSQLI_BOTH)) {
printf('[ { "TrafficLArray": [ { "UnixTime_green_1":"%s", "UnixTime_red_1":"%s", "UnixTime_green_2":"%s", "UnixTime_red_2":"%s", "Distance":"%s" } ] } ]',
$row['UnixTime_green_1'], $row['UnixTime_red_1'], $row['UnixTime_green_2'], $row['UnixTime_red_2'], $row['Distance']);
}
mysqli_close($con);
?>
MainActivity.java (фрагмент)
package com.example.xxx.xxx; // измените пакет
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.Volley;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
TextView results, recSpeed;
String JsonURL = "YourURLhere";
RequestQueue requestQueue;
private final static double maxSpeed = 18; // см/с
private double speed;
public final static int NUMBER_OF_ENTRIES = 2;
long[] unixTimesGreen = new long[NUMBER_OF_ENTRIES];
long[] unixTimesRed = new long[NUMBER_OF_ENTRIES];
private double distance;
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private Future<?> timingTask;
public void tick(long milliseconds) {
timingTask = scheduler.scheduleAtFixedRate(new Runnable() {
public void run() { updateTable(); }
}, 0, milliseconds, TimeUnit.MILLISECONDS);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
results = findViewById(R.id.jsonData);
recSpeed = findViewById(R.id.recommendedSpeed);
updateTable();
tick(500);
}
private void updateTable() {
requestQueue = Volley.newRequestQueue(this);
JsonArrayRequest arrayreq = new JsonArrayRequest(JsonURL,
new Response.Listener<JSONArray>() {
@Override
public void onResponse(JSONArray response) {
try {
JSONObject TrafficLObj = response.getJSONObject(0);
JSONArray trafficArry = TrafficLObj.getJSONArray("TrafficLArray");
for (int i = 0; i < trafficArry.length(); i++) {
JSONObject jsonObject = trafficArry.getJSONObject(i);
String[] green = new String[NUMBER_OF_ENTRIES];
String[] red = new String[NUMBER_OF_ENTRIES];
String distanceTemp = jsonObject.getString("Distance");
distance = (double) Long.parseLong(distanceTemp);
green[0] = jsonObject.getString("UnixTime_green_1");
red[0] = jsonObject.getString("UnixTime_red_1");
green[1] = jsonObject.getString("UnixTime_green_2");
red[1] = jsonObject.getString("UnixTime_red_2");
for (int j = 0; j < unixTimesRed.length; j++) {
unixTimesGreen[j] = Long.parseLong(green[j]);
unixTimesRed[j] = Long.parseLong(red[j]);
}
}
updateTextView();
} catch (JSONException e) { e.printStackTrace(); }
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) { Log.e("Volley", "Error"); }
});
requestQueue.add(arrayreq);
}
private void updateTextView() {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < unixTimesGreen.length; i++) {
builder.append("unixTimeGreen").append(i).append(": ")
.append(new Date(unixTimesGreen[i] * 1000)).append("\n");
builder.append("unixTimeRed").append(i).append(": ")
.append(new Date(unixTimesRed[i] * 1000)).append("\n");
}
builder.append("distance: ").append(distance);
speedCalc();
String s = String.format("%.0f", speed);
recSpeed.setText(s);
}
private void speedCalc() {
int interval = 0;
double time = (double) System.currentTimeMillis() / 1000;
for (int i = 0; i < NUMBER_OF_ENTRIES; i++) {
double diff = unixTimesRed[interval] - time;
speed = distance / (diff / 1000);
interval++;
}
interval--;
if (distance / (unixTimesGreen[interval] - time) > maxSpeed) speed = maxSpeed;
else speed = distance / (unixTimesGreen[interval] - time);
speed = speed * 5;
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.xxx.xxx"> <!-- измените пакет -->
<uses-permission android:name="android.permission.INTERNET"/>
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
strings.xml
<resources>
<string name="app_name">NooitRood</string> <!-- "Никогда не красный" -->
</resources>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
tools:context="com.example.xxx.xxx.MainActivity"
android:orientation="vertical">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingBottom="10dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="Рекомендуемая скорость:"
android:textSize="20sp"
android:textStyle="bold"
android:id="@+id/jsonData"
android:textColor="#000000" />
<TextView android:layout_width="900px"
android:layout_height="900px"
android:layout_gravity="center_horizontal"
android:paddingTop="10dp"
android:id="@+id/recommendedSpeed"
android:textSize="120sp"
android:textColor="#000000"
android:textStyle="bold"
android:gravity="center"
android:background="@drawable/gebodsbord"/>
</LinearLayout>
Схемы
Передатчик:
RF 433МГц: tx_pin → pin 3
HC-SR04: trig_pin → pin 5, echo_pin → pin 6
Приёмник (Arduino Yun):
RF 433МГц: rx_pin → pin 3
Светофор: RedLED → pin 5, OrangeLED → pin 6, GreenLED → pin 7
DS3231 RTC: SCL → SCL (pin 3), SDA → SDA (pin 2)
Производственный процесс
- Газовые фонари: эволюция, дизайн и современное производство
- Лазерный указатель: дизайн, производство и обзор безопасности
- Калейдоскоп: от науки Брюстера к современному дизайну
- Светофоры: инженерия, история и будущие инновации
- Светодиоды (LED): от зарождения к 100-летнему ресурсу
- Разборка умной лампы Sengled: технический разбор и обзор Zigbee-подключения
- Проектирование высокоточных Continuous-Wave ToF систем: комплексный обзор
- Умный светофор: адаптивное уличное освещение на базе IoT
- ИИ-управляемая система дальнего света улучшает ночное зрение и предотвращает слепоту
- Освоение аксессуаров для LED лент: подключение, управление и кастомизация